<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Kamal's Blog]]></title><description><![CDATA[Random notes around Linux, Python, Django and Vuejs]]></description><link>https://grep.koditi.my</link><generator>RSS for Node</generator><lastBuildDate>Sun, 12 Apr 2026 03:37:46 GMT</lastBuildDate><atom:link href="https://grep.koditi.my/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Podman in Incus container]]></title><description><![CDATA[Got this error:-
ERRO[0000] running `/usr/bin/newuidmap 738 0 1000 1 1 524288 65536`: newuidmap: write to uid_map failed: Operation not permitted Error: cannot set up namespace using "/usr/bin/newuidmap": should have setuid or have filecaps setuid: e...]]></description><link>https://grep.koditi.my/podman-in-incus-container</link><guid isPermaLink="true">https://grep.koditi.my/podman-in-incus-container</guid><category><![CDATA[podman]]></category><category><![CDATA[incus ]]></category><category><![CDATA[Fedora]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Sun, 30 Nov 2025 04:35:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764477252010/2c1ad38c-938e-44cd-badd-b5eff0e533fc.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Got this error:-</p>
<pre><code class="lang-plaintext">ERRO[0000] running `/usr/bin/newuidmap 738 0 1000 1 1 524288 65536`: newuidmap: write to uid_map failed: Operation not permitted Error: cannot set up namespace using "/usr/bin/newuidmap": should have setuid or have filecaps setuid: exit status 1
</code></pre>
<p>This is after setting the nesting config to true:-</p>
<pre><code class="lang-plaintext">incus config set &lt;container_name&gt; security.nesting "true"
</code></pre>
<p>We also need to set subid/subgid mapping inside the container:-</p>
<pre><code class="lang-plaintext">incus exec &lt;container_name&gt; -- sh -c "echo 'ubuntu:100000:65536' &gt; /etc/subuid"
incus exec &lt;container_name&gt; -- sh -c "echo 'ubuntu:100000:65536' &gt; /etc/subgid"
</code></pre>
<p>The <code>newuidmap</code> and <code>newgidmap</code> binaries must have explicit permissions to manipulate user and group namespaces. This fixes the <code>Operation not permitted: should have setuid/setgid</code> error.</p>
<pre><code class="lang-plaintext">incus exec &lt;container_name&gt; -- setcap cap_setuid+ep /usr/bin/newuidmap
incus exec &lt;container_name&gt; -- setcap cap_setgid+ep /usr/bin/newgidmap
</code></pre>
<p>To make the config easier, I put this in a cloud-init config to properly set up the container.</p>
<pre><code class="lang-plaintext">#cloud-config
users:
  - name: ubuntu
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: wheel
    shell: /usr/bin/zsh
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINEWBrd0A8TxQMU464x0DpU7q5rTkOJr0v/IWiub5h56 kamal@x1

packages:
  - zsh
  - git
  - vim
  - curl
  - openssh-server
  - util-linux-user       # for the `chsh` command
  - fira-code-fonts       # optional Nerd Font (remove if you don’t want it)
  - shadow-utils
  - podman
  - libcap

runcmd:
  - chown -R ubuntu:ubuntu /home/ubuntu
  - chmod 700 /home/ubuntu/.ssh
  - chmod 600 /home/ubuntu/.ssh/authorized_keys
  # 1. Force a temporary password ("temp") to initialize the account state.
  # This inserts the required hash into /etc/shadow.
  - echo "ubuntu:temp" | chpasswd

  # 2. Immediately remove the password hash (delete the password).
  # This makes the account passwordless while preserving the 'activated' state.
  - passwd -d ubuntu
  - systemctl enable sshd
  - systemctl start sshd
  - su - ubuntu -c 'sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" -- --unattended'
  - su - ubuntu -c "git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions"
  - su - ubuntu -c "git clone https://github.com/zsh-users/zsh-completions ~/.oh-my-zsh/custom/plugins/zsh-completions"
  - su - ubuntu -c "git clone https://github.com/zsh-users/zsh-syntax-highlighting ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting"
  - su - ubuntu -c 'echo "export PATH=\"\$HOME/.local/bin:\$PATH\"" &gt;&gt; /home/ubuntu/.zshrc'
  - su - ubuntu -c "mkdir -p /home/ubuntu/.local/bin"
  - su - ubuntu -c "curl -sS https://starship.rs/install.sh | sh -s -- -y --bin-dir /home/ubuntu/.local/bin"
  - su - ubuntu -c 'echo "eval \"\$(starship init zsh)\"" &gt;&gt; /home/ubuntu/.zshrc'
  - su - ubuntu -c "sed -i 's/^plugins=(git)/plugins=(git zsh-autosuggestions zsh-completions zsh-syntax-highlighting)/g' /home/ubuntu/.zshrc"
  - su - ubuntu -c "curl -LsSf https://astral.sh/uv/install.sh | sh"
  - su - ubuntu -c "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash"
  # Define variables for clarity
  - USERNAME="ubuntu"
  - START_ID="100000"
  - COUNT="65536"

  # --- Subordinate ID Mapping ---
  # Create/overwrite the required subordinate UID/GID entries for the default user.
  - sh -c "echo \"$USERNAME:$START_ID:$COUNT\" &gt;&gt; /etc/subuid"
  - sh -c "echo \"$USERNAME:$START_ID:$COUNT\" &gt;&gt; /etc/subgid"

  # --- Binary Capabilities Fix ---
  # Fix for 'newuidmap': grants capability to set UIDs.
  - sh -c "setcap cap_setuid+ep /usr/bin/newuidmap"

  # Fix for 'newgidmap': grants capability to set GIDs.
  - sh -c "setcap cap_setgid+ep /usr/bin/newgidmap"

  # --- Incus Nesting Dependencies (Optional but recommended for Fedora/Podman) ---
  # Ensure the necessary kernel modules are available for unprivileged overlay mounts
  - modprobe overlay
  - modprobe fuse
</code></pre>
<p>Now we can launch the container:-</p>
<pre><code class="lang-plaintext">incus launch images:fedora/42/cloud tmp --config=user.user-data="$(cat fedora-42.yml)" --config security.nesting=true
</code></pre>
<p>And test running podman:-</p>
<pre><code class="lang-plaintext">incus exec &lt;container_name&gt; -- su - ubuntu -c "podman run hello-world"
</code></pre>
<h2 id="heading-additional-notes">Additional notes</h2>
<p>To check the status of cloud-init execution inside the container:-</p>
<pre><code class="lang-plaintext">cloud-init status
tail -f /var/log/cloud-init-output.log
</code></pre>
]]></content:encoded></item><item><title><![CDATA[So You Want to Send an Email?]]></title><description><![CDATA[So, you tried sending an email from a new VPS for your app’s invitation feature, and… crickets. No email arrived. You thought it’d be as easy as hitting "send," but turns out, sending emails from a server is more like navigating a maze blindfolded. L...]]></description><link>https://grep.koditi.my/so-you-want-to-send-an-email</link><guid isPermaLink="true">https://grep.koditi.my/so-you-want-to-send-an-email</guid><category><![CDATA[Devops]]></category><category><![CDATA[email]]></category><category><![CDATA[postfix]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Mon, 23 Jun 2025 07:28:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750663562729/218c7491-e8e1-4893-ba44-b9624023c034.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So, you tried sending an email from a new VPS for your app’s invitation feature, and… crickets. No email arrived. You thought it’d be as easy as hitting "send," but turns out, sending emails from a server is more like navigating a maze blindfolded. Let’s unpack why it didn’t work and how to get you up to speed on this critical DevOps skill.</p>
<h2 id="heading-why-email-sending-isnt-simple">Why Email Sending Isn’t Simple</h2>
<p>Sending emails from a server isn’t like sending one from Gmail. Your app needs a way to talk to other email servers, and that’s not plug-and-play. Without the right setup, your emails might get blocked, flagged as spam, or just vanish. The main hurdles are:</p>
<ul>
<li><p>You need software to send emails.</p>
</li>
<li><p>Your VPS provider might block the ports used for email.</p>
</li>
<li><p>Other servers won’t trust your VPS unless you prove you’re legit.</p>
</li>
<li><p>New VPS IPs have no reputation, so emails often land in spam.</p>
</li>
</ul>
<p>Let’s break down what you need to know.</p>
<h2 id="heading-the-role-of-an-mta">The Role of an MTA</h2>
<p>To send emails, your server needs a Mail Transfer Agent (MTA) like Postfix or Exim. Think of it as your server’s post office—it handles the delivery of emails using SMTP (Simple Mail Transfer Protocol). Without an MTA, your app’s emails have nowhere to go.</p>
<p>Setting up an MTA involves installing it (e.g., Postfix) and configuring it with your domain. But even then, you’re not done—logs are your best friend for troubleshooting why an email didn’t make it.</p>
<h2 id="heading-dealing-with-port-blocks">Dealing with Port Blocks</h2>
<p>Here’s a common gotcha: most VPS providers block port 25, the default for SMTP, to stop spammers. You’ll need to check if it’s open or use alternatives like port 587. Contact your provider to unblock ports if needed, but be prepared for some back-and-forth.</p>
<p>If ports are a hassle, there’s an easier way we’ll cover soon.</p>
<h2 id="heading-making-emails-trustworthy">Making Emails Trustworthy</h2>
<p>Even with an MTA and open ports, email servers are suspicious. They’ll reject or spam-flag your emails unless you set up authentication:</p>
<ul>
<li><p>SPF: A DNS record that lists which IPs can send emails for your domain.</p>
</li>
<li><p>DKIM: Signs emails with a cryptographic key to prove they’re from you.</p>
</li>
<li><p>DMARC: Tells other servers what to do if authentication fails.</p>
</li>
</ul>
<p>These are set up through your domain’s DNS provider. Tools like MXToolbox can help you verify everything’s working. Without these, your emails are like strangers knocking on a door—nobody’s letting them in.</p>
<h2 id="heading-the-shortcut-smtp-relays">The Shortcut: SMTP Relays</h2>
<p>Running your own mail server is a lot of work, like cooking a gourmet meal from scratch. A simpler option is using a third-party SMTP relay like SendGrid, Amazon SES, or Mailgun. These services handle ports, authentication, and IP reputation for you. You just plug their credentials into your app’s email settings and start sending.</p>
<p>Relays are often the go-to for production apps because they’re reliable and save you from playing whack-a-mole with deliverability issues.</p>
<h2 id="heading-leveling-up-your-skills">Leveling Up Your Skills</h2>
<p>You didn’t know this stuff off the bat, and that’s okay—email setup is a learned skill. To get better:</p>
<ul>
<li><p>Practice: Set up Postfix on a test VPS and send emails. Try configuring SPF and DKIM for a test domain.</p>
</li>
<li><p>Read: Check out Postfix docs, DigitalOcean’s email guides, or SendGrid’s integration tips.</p>
</li>
<li><p>Logs: Get cozy with /var/log/mail.log. It’s your map to fixing issues.</p>
</li>
<li><p>Relays: Experiment with a service like SendGrid to see how it simplifies things.</p>
</li>
<li><p>Mentorship: Pair with a senior DevOps engineer for config reviews and weekly check-ins.</p>
</li>
</ul>
<p>To fix the immediate issue:</p>
<ol>
<li><p>Check if an MTA like Postfix is installed. If not, install it.</p>
</li>
<li><p>Verify SMTP ports are open. If blocked, contact your provider or use a relay.</p>
</li>
<li><p>Test sending an email and check logs for errors.</p>
</li>
<li><p>If it’s still broken, set up a relay like SendGrid for a quick win.</p>
</li>
</ol>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Sending emails from a VPS is trickier than it looks, but it’s a must-have skill for DevOps. By understanding MTAs, port blocks, authentication, and relays, you’ll get those emails flowing and avoid future headaches. Got any email horror stories? Share ’em below—I’d love to hear how you tackled them!</p>
<p><em>Image by pexels.</em></p>
]]></content:encoded></item><item><title><![CDATA[Reuse existing ssh agent]]></title><description><![CDATA[I have a simple script that I run to load my ssh keys into ssh agent. It looks like this:-
eval "$(ssh-agent)"
ssh-add

The problem is, I have to run this in every new terminal I started. And that means typing the key passphrase every single time. Si...]]></description><link>https://grep.koditi.my/reuse-existing-ssh-agent</link><guid isPermaLink="true">https://grep.koditi.my/reuse-existing-ssh-agent</guid><category><![CDATA[Linux]]></category><category><![CDATA[ssh]]></category><category><![CDATA[Bash]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Tue, 26 Nov 2024 23:42:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732664641521/dea988a1-11e4-45d4-8f3f-939f9fc2dd6a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have a simple script that I run to load my ssh keys into ssh agent. It looks like this:-</p>
<pre><code class="lang-bash"><span class="hljs-built_in">eval</span> <span class="hljs-string">"<span class="hljs-subst">$(ssh-agent)</span>"</span>
ssh-add
</code></pre>
<p>The problem is, I have to run this in every new terminal I started. And that means typing the key passphrase every single time. Since all it does is just running the agent and then setting environment variable pointing to the agent’s socket, made me thinking what if I can just re-use the existing agent instead of running new one.</p>
<p>These days, once you know what you want to have, it just a matter of prompting AI chatbot to write the code for you. I use Claude most of the time for coding, so this is what I got after a few prompts:-</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-comment">#eval "$(ssh-agent)"</span>
<span class="hljs-comment">#ssh-add</span>

<span class="hljs-comment"># Function to find existing SSH agent</span>
<span class="hljs-function"><span class="hljs-title">find_ssh_agent</span></span>() {
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Look for existing ssh-agent processes"</span>
    AGENT_PID=$(pgrep -u <span class="hljs-variable">$USER</span> ssh-agent)
    <span class="hljs-keyword">if</span> [ -n <span class="hljs-string">"<span class="hljs-variable">$AGENT_PID</span>"</span> ]; <span class="hljs-keyword">then</span>
        <span class="hljs-comment"># Try to find socket in standard locations</span>
        <span class="hljs-keyword">for</span> SOCKET <span class="hljs-keyword">in</span> /tmp/ssh-*/agent.*; <span class="hljs-keyword">do</span>
            <span class="hljs-built_in">echo</span> <span class="hljs-variable">$SOCKET</span>
            <span class="hljs-keyword">if</span> [ -S <span class="hljs-string">"<span class="hljs-variable">$SOCKET</span>"</span> ]; <span class="hljs-keyword">then</span>  <span class="hljs-comment"># Check if it's a valid socket</span>
                <span class="hljs-comment"># Test the socket</span>
                SSH_AUTH_SOCK=<span class="hljs-variable">$SOCKET</span> ssh-add -l &gt;/dev/null 2&gt;&amp;1
                <span class="hljs-keyword">if</span> [ $? -ne 2 ]; <span class="hljs-keyword">then</span>  <span class="hljs-comment"># Exit code 2 means socket is invalid</span>
                    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Found socket <span class="hljs-variable">$SOCKET</span>"</span>
                    <span class="hljs-built_in">export</span> SSH_AUTH_SOCK=<span class="hljs-variable">$SOCKET</span>
                    <span class="hljs-built_in">export</span> SSH_AGENT_PID=<span class="hljs-variable">$AGENT_PID</span>
                    <span class="hljs-built_in">return</span> 0
                <span class="hljs-keyword">fi</span>
            <span class="hljs-keyword">fi</span>
        <span class="hljs-keyword">done</span>
    <span class="hljs-keyword">fi</span>
    <span class="hljs-built_in">return</span> 1
}

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Try to find existing agent first"</span>
<span class="hljs-keyword">if</span> ! find_ssh_agent; <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"If no agent found, start a new one"</span>
    <span class="hljs-built_in">eval</span> <span class="hljs-string">"<span class="hljs-subst">$(ssh-agent)</span>"</span> &gt; /dev/null
<span class="hljs-keyword">fi</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Check if keys are already added"</span>
ssh-add -l &gt; /dev/null 2&gt;&amp;1
<span class="hljs-keyword">if</span> [ $? -eq 1 ]; <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"No identities found, add default keys"</span>
    ssh-add
<span class="hljs-keyword">fi</span>
</code></pre>
<p>I saved this in file called <code>~/.loadkeys.sh</code> and run it every time I start a new terminal.</p>
<p><em>Image is from Pexels.</em></p>
]]></content:encoded></item><item><title><![CDATA[Deploying web app with Nginx and Cloudflare]]></title><description><![CDATA[This setup is for Django app but can also applies to other platform as well. The general setup before we get into details:-

Run django with gunicorn

Manage the gunicorn process with systemd

Nginx as reverse proxy that forward incoming requests to ...]]></description><link>https://grep.koditi.my/deploying-web-app-with-nginx-and-cloudflare</link><guid isPermaLink="true">https://grep.koditi.my/deploying-web-app-with-nginx-and-cloudflare</guid><category><![CDATA[nginx]]></category><category><![CDATA[cloudflare]]></category><category><![CDATA[Django]]></category><category><![CDATA[#Lightsail]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Thu, 14 Nov 2024 23:49:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731628030788/3fb5aa73-58e9-406e-b232-f10fdf17c013.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This setup is for Django app but can also applies to other platform as well. The general setup before we get into details:-</p>
<ul>
<li><p>Run django with gunicorn</p>
</li>
<li><p>Manage the gunicorn process with systemd</p>
</li>
<li><p>Nginx as reverse proxy that forward incoming requests to gunicorn</p>
</li>
<li><p>Nginx serve https, http requests will be redirected to https</p>
</li>
<li><p>Nginx use Cloudflare origin certificate for the https - the cert expiry date is 2038 so we don’t have to worry about renewing the cert</p>
</li>
<li><p>Configure server’s firewall to only accept HTTP/S connections from cloudflare only. Cloudflare publish list of their <a target="_blank" href="https://www.cloudflare.com/ips-v4/#">IPs</a> that we can scrape and pass it to our firewall config.</p>
</li>
</ul>
<p>For the nginx config, we want to achieve the following:-</p>
<ul>
<li><p>The site can be protected with basic auth (useful for pre-launch) but certain paths can be exempted - for external webhooks</p>
</li>
<li><p>Serve static files such as js. css and images directly</p>
</li>
<li><p>Redirect http requests to https</p>
</li>
<li><p>Set <code>X-Forwarded-For</code> to client actual IP</p>
</li>
</ul>
<p>Here’s the example nginx config:-</p>
<pre><code class="lang-bash">server {
        <span class="hljs-comment">#listen 80 default_server;</span>
        listen [::]:80 default_server;
        client_max_body_size 2M;

        <span class="hljs-comment"># SSL configuration</span>
        <span class="hljs-comment">#</span>
        listen 443 ssl default_server;
        listen [::]:443 ssl default_server;
        ssl_certificate /etc/ssl/certs/cloudflare.pem;
        ssl_certificate_key     /etc/ssl/private/cloudflare.key;

        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location @backend {
                real_ip_header CF-Connecting-IP;
                proxy_set_header Host <span class="hljs-variable">$host</span>;
                proxy_set_header X-Forwarded-For <span class="hljs-variable">$http_CF_Connecting_IP</span>;
                proxy_pass http://127.0.0.1:8000;
        }

        location /static/ {
                <span class="hljs-built_in">alias</span> /app/appname/current/public/;
                <span class="hljs-comment">#expires 30d;</span>
                add_header Cache-Control public;
        }
        location /media/ {
                <span class="hljs-built_in">alias</span> /app/appname/media/;
                <span class="hljs-comment">#expires 30d;</span>
                add_header Cache-Control public;
        }

        location / {
                <span class="hljs-comment">#auth_basic "Restricted Area";</span>
                <span class="hljs-comment"># auth_basic_user_file /etc/nginx/htpasswd;</span>

                location /stripe/ {
                        auth_basic off;
                        try_files <span class="hljs-variable">$uri</span> @backend;
                }
                location /paddle/callback/ {
                        auth_basic off;
                        try_files <span class="hljs-variable">$uri</span> @backend;
                }
                <span class="hljs-comment"># First attempt to serve request as file, then</span>
                <span class="hljs-comment"># as directory, then fall back to displaying a 404.</span>
                <span class="hljs-comment"># try_files $uri $uri/ =404;</span>
                try_files <span class="hljs-variable">$uri</span> @backend;
        }
}


<span class="hljs-comment"># Virtual Host configuration for example.com</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># You can move that to a different file under sites-available/ and symlink that</span>
<span class="hljs-comment"># to sites-enabled/ to enable it.</span>
<span class="hljs-comment">#</span>
server {
        listen 80;
        listen [::]:80;

        server_name example.com;
        <span class="hljs-built_in">return</span> 301 https://<span class="hljs-variable">$server_name</span><span class="hljs-variable">$request_uri</span>;
}
</code></pre>
<p>And this is the systemd unit file:-</p>
<pre><code class="lang-bash">[Unit]
Description=appname daemon
After=network.target

[Service]
User=appname
Group=appname
WorkingDirectory=/app/appname/current
Environment=<span class="hljs-string">"PYTHONPATH=<span class="hljs-variable">$PYTHONPATH</span>:/app/appname/current/src"</span>
ExecStart=/app/appname/current/.venv/bin/gunicorn -t 60 --workers 5 appname.wsgi:application

[Install]
WantedBy=multi-user.target
</code></pre>
<p>Add this at <code>/etc/systemd/system/appname.service</code>.</p>
<p>Here’s the script to scrape cloudflare’s IPs and update Lightsail firewall to allow connections from that IP to port 80 and 443. When using Lightsail you can run this script from Cloudshell in the same region. It super convenient as you don’t have to worry about AWS credentials, the cloudshell instance already assume IAM roles and have full access (as of your IAM permissions) to AWS resources.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> boto3
<span class="hljs-keyword">import</span> requests

<span class="hljs-comment"># Set up the Lightsail client</span>
client = boto3.client(<span class="hljs-string">'lightsail'</span>)

<span class="hljs-comment"># Define the instance name</span>
instance_name = <span class="hljs-string">'appname-01'</span>

resp = requests.get(<span class="hljs-string">"https://www.cloudflare.com/ips-v4/"</span>)
ips = resp.text.split(<span class="hljs-string">"\n"</span>)
print(ips)
<span class="hljs-comment"># Define the ports and allowed IPs</span>
port_info = [
    {
        <span class="hljs-string">'fromPort'</span>: <span class="hljs-number">80</span>,
        <span class="hljs-string">'toPort'</span>: <span class="hljs-number">80</span>,
        <span class="hljs-string">'protocol'</span>: <span class="hljs-string">'tcp'</span>,
        <span class="hljs-string">'cidrs'</span>: ips,  <span class="hljs-comment"># Example IP ranges</span>
        <span class="hljs-string">'cidrListAliases'</span>: []  <span class="hljs-comment"># You can use predefined IP lists here if needed</span>
    },
    {
        <span class="hljs-string">'fromPort'</span>: <span class="hljs-number">443</span>,
        <span class="hljs-string">'toPort'</span>: <span class="hljs-number">443</span>,
        <span class="hljs-string">'protocol'</span>: <span class="hljs-string">'tcp'</span>,
        <span class="hljs-string">'cidrs'</span>: ips,  <span class="hljs-comment"># Example IP ranges</span>
        <span class="hljs-string">'cidrListAliases'</span>: []  <span class="hljs-comment"># You can use predefined IP lists here if needed</span>
    },
    {
        <span class="hljs-string">'fromPort'</span>: <span class="hljs-number">22</span>,
        <span class="hljs-string">'toPort'</span>: <span class="hljs-number">22</span>,
        <span class="hljs-string">'protocol'</span>: <span class="hljs-string">'tcp'</span>,
        <span class="hljs-string">'cidrs'</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],  <span class="hljs-comment"># Example IP ranges</span>
        <span class="hljs-string">'cidrListAliases'</span>: []
    }
]

<span class="hljs-comment"># Apply the new firewall rules</span>
response = client.put_instance_public_ports(
    instanceName=instance_name,
    portInfos=port_info
)
</code></pre>
<h2 id="heading-ending-notes">Ending notes</h2>
<p>Make sure to never leak your origin server ip address. Common mistake is to have another entry in dns pointing to the server’s ip, such as MX record. If you need to receive emails, use different server for that.</p>
]]></content:encoded></item><item><title><![CDATA[Setting up dev environment with code-server and LXC]]></title><description><![CDATA[Some upfront notes - I did this a month ago. At that time, I haven't look into Cloudflare ZeroTrust and tunnel yet. So if I'm doing this now, I probably would do it differently for the networking part.
Let say we have some beefy server with GPU that ...]]></description><link>https://grep.koditi.my/setting-up-dev-environment-with-code-server-and-lxc</link><guid isPermaLink="true">https://grep.koditi.my/setting-up-dev-environment-with-code-server-and-lxc</guid><category><![CDATA[Docker]]></category><category><![CDATA[containers]]></category><category><![CDATA[vscode]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Tue, 06 Feb 2024 14:21:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1707229563530/12a5095d-eb77-46c2-97df-13d4125f1e19.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Some upfront notes - I did this a month ago. At that time, I haven't look into Cloudflare ZeroTrust and tunnel yet. So if I'm doing this now, I probably would do it differently for the networking part.</p>
<p>Let say we have some beefy server with GPU that previously being used for ML works but now sitting idle. A perfect candidate for shared development server. We're currently using Gitpod and while it's been a great experience, not so much for the wallet.</p>
<p>Back in 2010 when we started doing remote work, we also practiced remote dev with a no local code checkout policy. So instead of working on their own computer, developer works on remote EC2 instance. It pretty simple at that time since everyone just use Vim and works inside screen session to get continuous remote sessions. Couple with ssh port forwarding, it has everything that we need.</p>
<p>Fast forward 12 years later, the nature of web development has changed a lot. Frontend tooling use quite a lot of resources and developer also want to use VSCode instead of Vim. Shared server no longer an options.</p>
<p>But what if we can still use shared server with standalone experiences? Enter LXC. People probably knows Docker more than LXC even though they actually using same set of technology such as namespaces and cgroups. The main difference probably we can say docker is used for process container while lxc for OS container, although nothing stop us to use them vice-versa. LXC would give us the traditional VPS thing.</p>
<p>So the idea is that everyone would works inside their own LXC instance.</p>
<h2 id="heading-the-problem">The Problem</h2>
<p>Shared server is not possible because the app being setup in a way that everyone need to run postgresql on port 5432, redis on port 6379, app on port 8000 and so on. Of course we can assign everyone different ports but that's messy.</p>
<p>LXC solve this problem partly but not all. While everyone can run all the services on the ports mentioned above inside LXC, how do we access them from outside?</p>
<h2 id="heading-nginx-for-routing">Nginx for routing</h2>
<p>Gitpod give some ideas on how to route external request to your app running inside the container. For example if your app is running at port 8000, your app url will be:-</p>
<p>https://8000-appname-workspaceid.workspacehost.gitpod.io/</p>
<p>So how do we achieve same url scheme for our dev lxc container? Our nginx config looks like this:-</p>
<pre><code class="lang-nginx"><span class="hljs-section">server</span> {
    <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl;
    <span class="hljs-attribute">server_name</span> ~^(?&lt;port&gt;\d+)-(?&lt;myhost&gt;[^.]+)\.home.lab$;
    <span class="hljs-attribute">include</span> snippets/home-lab-ssl.conf;

    <span class="hljs-attribute">location</span> / {
        <span class="hljs-attribute">resolver</span> <span class="hljs-number">127.0.0.53</span>;
        <span class="hljs-attribute">set</span> <span class="hljs-variable">$target_port</span> <span class="hljs-variable">$port</span>;
        <span class="hljs-attribute">set</span> <span class="hljs-variable">$target_host</span> <span class="hljs-variable">$myhost</span>;
        <span class="hljs-attribute">rewrite</span>           <span class="hljs-regexp"> ^(.*)$</span>   <span class="hljs-string">"://<span class="hljs-variable">$http_host</span><span class="hljs-variable">$uri</span><span class="hljs-variable">$is_args</span><span class="hljs-variable">$args</span>"</span>;
        <span class="hljs-attribute">rewrite</span>           <span class="hljs-regexp"> ^(.*)$</span>   <span class="hljs-string">"http<span class="hljs-variable">$uri</span><span class="hljs-variable">$is_args</span><span class="hljs-variable">$args</span>"</span> <span class="hljs-literal">break</span>;
        <span class="hljs-attribute">proxy_http_version</span> <span class="hljs-number">1</span>.<span class="hljs-number">1</span>;
        <span class="hljs-attribute">proxy_set_header</span> Upgrade <span class="hljs-variable">$http_upgrade</span>;
        <span class="hljs-attribute">proxy_set_header</span> Connection <span class="hljs-string">"upgrade"</span>;
        <span class="hljs-attribute">proxy_set_header</span> Origin https://<span class="hljs-variable">$host</span>;
        <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;
        <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-For <span class="hljs-variable">$remote_addr</span>;
        <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto https;
        <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Ssl <span class="hljs-literal">on</span>;
        <span class="hljs-attribute">proxy_pass</span> http://<span class="hljs-variable">$target_host</span>.lab:<span class="hljs-variable">$target_port</span><span class="hljs-variable">$request_uri</span>;
    }
}
</code></pre>
<p>This allow us to have url scheme likthis:-</p>
<p><a target="_blank" href="https://3000-ziddan.kaid.lab/?folder=/home/ubuntu/git/medan">https://3000-kamal.home.lab/</a></p>
<p>Request to above url will be forwarded by nginx running on the host server to the port 3000 of the container. This solve the routing problem but how about the networking?</p>
<h2 id="heading-ssh-socks-proxy">SSH Socks Proxy</h2>
<p>So the domain kamal.home.lab above is just made up domain that don't really exists. The easiest way to make the domain resolvable is by adding it to <code>/etc/hosts</code>. As mentioned at the beginning of this article, I would probably look into Cloudflare ZeroTrust if I'm doing it now.</p>
<p>My <code>/etc/hosts</code> on the host server looks like this:-</p>
<pre><code class="lang-bash">192.168.10.100 8080-kamal.home.lab
192.168.10.100 3000-kamal.home.lab
10.201.33.17 kamal.lab
</code></pre>
<p>192.168.10.100 is the host IP address and 10.201.33.17 is the lxc container instance. LXC is configured with bridge networking so the container instances looks like another host in the network. That solve the networking inside the server. But how to access them from my laptop?</p>
<p>This is where ZeroTrust or Tailscale would help but for now I'll just use SSH socks proxy. From my laptop I would do the following:-</p>
<pre><code class="lang-bash">ssh server-ip -D 5555
</code></pre>
<p>That would create a socks tunnel from my laptop to the server. The next step is to configure my browser (firefox) under the networking settings. Pick Manual proxy configuration and specify SOCKS host 127.0.0.1 and port number 5555. Next is the most important - tick Proxy DNS when using SOCKS v5. This will force Firefox to make dns request through the socks tunnel as well.</p>
<h2 id="heading-dns-resolving">DNS Resolving</h2>
<p>Our browser already making dns query to our server so we need to something to answer the query, or in other words run a DNS server. Fortunately systemd comes with systemd-resolvd, a lightweight dns server. And by default it will look into <code>/etc/hosts</code> that we enter our records above. Perfect! Just make sure resolvd is running and we're done.</p>
<h2 id="heading-ssl-setup">SSL Setup</h2>
<p>At this point we can already access any tcp ports inside our lxc instance from our browser but only with plain http. Many apps these days that use websocket will require the connection to be in https, including code-server. Remember our url should be like this:-</p>
<p><a target="_blank" href="https://3000-ziddan.kaid.lab/?folder=/home/ubuntu/git/medan">https://3000-kamal.home.lab/</a></p>
<p>We will just use self-signed certificate for this. I'll skip how to generate them as there are plenty of tutorials you can find on now to generate self-signed cert. What's important is for you to add the cert to your browser's certificate list otherwise you'll get the infamous certificate warning.</p>
<p>Installing code-server</p>
<p>First we need to get into our lxc instance. We can use this command:-</p>
<pre><code class="lang-bash">lxc <span class="hljs-built_in">exec</span> --user 1000 --group 1000 --env HOME=/home/ubuntu/ -- /bin/bash --login
</code></pre>
<p>It's quite long and fortunately lxc provides <code>alias</code> command so we can create alias such as login, that allow us to use shorter command to get into our instance:-</p>
<pre><code class="lang-bash">lxc login container-name
</code></pre>
<p>For installing coder, just follow the <a target="_blank" href="https://github.com/coder/code-server?tab=readme-ov-file">instructions</a> to use the install script. Take notes here, there are 2 projects - Coder and code-server which is part of Coder. Coder is complete development environment similar to Gitpod or Github Codespaces. But we only want code-server here so make sure to install the correct app.</p>
<p>Another notes, you might see instruction to run command such as:-</p>
<pre><code class="lang-bash">sudo systemctl <span class="hljs-built_in">enable</span> code-server@<span class="hljs-variable">$USER</span>
</code></pre>
<p>and run into errors. That's because <code>$USER</code> environment variable is not set, so we need to fix command to login above or just use the actual user name. We almost done now. Accessing Github to clone our git repos can be done from code-server itself.</p>
]]></content:encoded></item><item><title><![CDATA[Running Django with Nginx Unit]]></title><description><![CDATA[I first discovered Unit back in 2019, when it was first released. Submitted a couple of issues on their Github. I was quite interested but it's not ready yet at that time.
Then I forgot, until yesterday. Was reading some discussion (forgot where) and...]]></description><link>https://grep.koditi.my/running-django-with-nginx-unit</link><guid isPermaLink="true">https://grep.koditi.my/running-django-with-nginx-unit</guid><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><category><![CDATA[nginx]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Wed, 01 Nov 2023 10:08:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1698833271407/5006cd55-8ff1-499f-9728-f5093f95b35f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I first discovered Unit back in 2019, when it was first released. Submitted a couple of <a target="_blank" href="https://github.com/nginx/unit/issues?q=is%3Aissue+author%3Ak4ml+">issues</a> on their Github. I was quite interested but it's not ready yet at that time.</p>
<p>Then I forgot, until yesterday. Was reading some discussion (forgot where) and someone mentioned Unit in the comment. Damn, I should check it back.</p>
<p>Nginx Unit is described as a universal application server, as it can run many different languages such as Python, PHP, Java, Ruby, Nodejs, Go and so on. All from the same <code>unit</code> executable, no need for extra program.</p>
<p>My current deployment setup right now is using Podman running under systemd. While it seems to work, there are a few things that left me unsatisfied:-</p>
<ul>
<li><p>It uses podman 3.4 as that the latest version available in Ubuntu 22.04. Podman 4 has a better way of running under systemd.</p>
</li>
<li><p>Running under systemd as the system's unit file with <code>User=</code> is not supported. So you're left with running it as a user's services. I'm fine with this, and this is what I prefer except ...</p>
</li>
<li><p>Systemd user's service only meant to last for the login user's session. You log out, it's gone. Wth. So it's meant for an interactive session, iow desktop, not server.</p>
</li>
<li><p>The workaround is to run <code>loginctl enable-linger</code>. This would let the service continue running even after you have logout.</p>
</li>
</ul>
<p>So when Nginx Unit came back into the picture, I was thrilled. Unit configuration is done through HTTP API. You submit the config as json string. This is great for automation. No more <code>sed</code> to replace some placeholder in nginx config. Everything can be done programmatically.</p>
<p>The documentation is also very complete now, to the extent that this blog post is not needed actually. But I'll just add some extra notes that may not easily found in the docs. For the rest, just refer to their <a target="_blank" href="https://unit.nginx.org/howto/django/">Django docs</a>.</p>
<p>So the extra notes:-</p>
<ul>
<li><p>When serving static files through routes match, the matched path is not stripped of the <code>$uri</code> value. So if you have url path such as <code>/static/css/app.css</code>, you need to have a directory <code>static</code> as well. In other words, your <code>share</code> value has to be <code>/path/to/app/$uri</code> and not <code>/path/to/app/static/$uri</code>. Because <code>/static</code> also exists in <code>$uri</code>.</p>
</li>
<li><p>Also static files, it served by the router process which runs under a different user than the user we specified for our app, so need to make sure that the user has permission to read the static files. In my case, the app is running as a user <code>myapp</code> but the router process is running under the user <code>unit</code>. You can check with <code>ps auxw | grep unit</code>. There are 3 processes run by unit. The first is <code>unitd</code> runs as root, and then <code>router</code> and <code>controller</code> process running under the user <code>unit</code>.</p>
</li>
</ul>
<p>That's all for now. I'll add more as I spend more time with Unit.</p>
]]></content:encoded></item><item><title><![CDATA[Django: Creating models dynamically]]></title><description><![CDATA[My original idea with siteplan was to allow a quick way to start and run a new django project (or app). The official way is to always start a minimal app that contains at least the following:

models.py

settings.py

urls.py


Apparently, you need th...]]></description><link>https://grep.koditi.my/django-creating-models-dynamically</link><guid isPermaLink="true">https://grep.koditi.my/django-creating-models-dynamically</guid><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Mon, 11 Sep 2023 00:19:05 GMT</pubDate><content:encoded><![CDATA[<p>My original idea with <a target="_blank" href="https://github.com/k4ml/siteplan">siteplan</a> was to allow a quick way to start and run a new django project (<a target="_blank" href="https://dev.to/k4ml/django-moving-away-from-project-vs-app-dichotomy-3e7">or app</a>). The official way is to always start a minimal app that contains at least the following:</p>
<ul>
<li><p>models.py</p>
</li>
<li><p>settings.py</p>
</li>
<li><p>urls.py</p>
</li>
</ul>
<p>Apparently, you need these 3 files to start Django project the usual way. Most python microframework like Flask or Bottle allows you to start your new app in just a single file. Imagine you can do something like this:-</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> siteplan <span class="hljs-keyword">import</span> App, run
<span class="hljs-keyword">from</span> django.http <span class="hljs-keyword">import</span> HttpResponse

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span>(<span class="hljs-params">request</span>):</span>
    <span class="hljs-keyword">return</span> HttpResponse(<span class="hljs-string">"Hello world"</span>)

<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path
urlpatterns = [
    path(<span class="hljs-string">"test/"</span>, index),
]

conf = {}
app = App(conf, urls=urlpatterns)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    run(app)
</code></pre>
<p>Trying to run Django from a single file was one of my favorite past time activities but most failed, due to the constraints mentioned above.</p>
<p>I have almost gotten it work with siteplan, except when it comes to defining your model. In the above example, if you try to define a models like this:-</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> siteplan <span class="hljs-keyword">import</span> App, run
<span class="hljs-keyword">from</span> django.http <span class="hljs-keyword">import</span> HttpResponse
<span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestModel</span>(<span class="hljs-params">models.Model</span>):</span>
    name = models.CharField(max_length=<span class="hljs-number">255</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span>(<span class="hljs-params">request</span>):</span>
    <span class="hljs-keyword">return</span> HttpResponse(<span class="hljs-string">"Hello world"</span>)
</code></pre>
<p>It will fail with this error right away:-</p>
<pre><code class="lang-python"> File <span class="hljs-string">"/home/kamal/.cache/pypoetry/virtualenvs/siteplan-G2sCj5Qw-py3.11/lib/python3.11/site-packages/django/conf/__init__.py"</span>, line <span class="hljs-number">63</span>, <span class="hljs-keyword">in</span> _setup
    <span class="hljs-keyword">raise</span> ImproperlyConfigured(
django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are <span class="hljs-keyword">not</span> configured. You must either define the environment variable DJANGO_SETTINGS_MODULE <span class="hljs-keyword">or</span> call settings.configure() before accessing settings.
</code></pre>
<p>So you can only import <code>django.db.models</code> after your app has been fully initialized. My idea was to create a dummy <code>siteplan.models</code> and later on attach the models that the user has defined in their <code>app.py</code> to <code>siteplan.models</code>.</p>
<p>Even though not ideals, just for some experimentation I try to defer the models initialization by defining the models in a function which then gets passed to the <code>App</code> instance. This way I evaluate the models definition after I have configured the settings.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_models</span>():</span>
    <span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestModel</span>(<span class="hljs-params">models.Model</span>):</span>
        name = models.CharField(max_length=<span class="hljs-number">255</span>)

        __module__ = <span class="hljs-string">"siteplan.models"</span>

    <span class="hljs-keyword">return</span> [TestModel]

conf = {}
app = App(conf, urls=urlpatterns, models=create_models)
</code></pre>
<p>Then in <code>App</code> class's <code>__init__</code>:-</p>
<pre><code class="lang-python">        default_conf.update(conf)
        base_conf.update(default_conf)

        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> settings.configured:
            settings.configure(**base_conf)

        <span class="hljs-keyword">from</span> siteplan <span class="hljs-keyword">import</span> models <span class="hljs-keyword">as</span> models_module
        <span class="hljs-keyword">for</span> mod <span class="hljs-keyword">in</span> self.models():
            setattr(models_module, mod.__name__, mod)
</code></pre>
<p>While this get rid of the settings error, we got another error:-</p>
<pre><code class="lang-python"> File <span class="hljs-string">"/home/kamal/.cache/pypoetry/virtualenvs/siteplan-G2sCj5Qw-py3.11/lib/python3.11/site-packages/django/apps/registry.py"</span>, line <span class="hljs-number">136</span>, <span class="hljs-keyword">in</span> check_apps_ready
    <span class="hljs-keyword">raise</span> AppRegistryNotReady(<span class="hljs-string">"Apps aren't loaded yet."</span>)
django.core.exceptions.AppRegistryNotReady: Apps aren<span class="hljs-string">'t loaded yet.</span>
</code></pre>
<p>This means we can only initialize the models after <code>AppConfig.ready()</code> method being called. This is tricky since <code>AppConfig</code> is a different class and we can only tell Django the path to that class.</p>
<p>I took a step back and searched around to see if it is possible to create django models dynamically. Turns out in some ways it is possible to <a target="_blank" href="https://code.djangoproject.com/wiki/DynamicModels">create models dynamically</a> even since django 1.0!</p>
<p>Baserow has been utilizing the method to the fullest as described in this <a target="_blank" href="https://baserow.io/blog/how-baserow-lets-users-generate-django-models">blog post</a>.</p>
<p>Even though I'm already at the losing end, but just for the sake of experimentation, I try to move initializing the models to the <code>__call__</code> method of <code>Siteplan</code>. This finally works but definitely can't be used as the method will be called on every request.</p>
<p>Being able to define the models is one thing, but how to create the database tables? In normal Django, we generate a migrations file and then call the <code>migrate</code> command. Thanks to the Baserow's article above, I learned that we don't need to generate migrations files in order to create the database tables:-</p>
<pre><code class="lang-python">    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__call__</span>(<span class="hljs-params">self, environ, start_response</span>):</span>
        ...
        <span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> connection
        <span class="hljs-keyword">for</span> mod <span class="hljs-keyword">in</span> self.models():
            setattr(models_module, mod.__name__, mod)

            <span class="hljs-keyword">with</span> connection.schema_editor() <span class="hljs-keyword">as</span> schema_editor:
                schema_editor.create_model(mod)

        <span class="hljs-keyword">return</span> application(environ, start_response)
</code></pre>
<p>Django <a target="_blank" href="https://docs.djangoproject.com/en/4.0/ref/schema-editor/">Schema Editor</a> allows us to create database tables without migrations files!</p>
<p>If I still want to proceed, I can let user define the models either in dummy class or in yaml, and then use the same approach used by baserow to generate the models dynamically.</p>
<p>Some other unrelated notes of what I bumped into in the course of this exercise:-</p>
<ul>
<li><p>Django fastapi bridge</p>
</li>
<li><p>Django-ninja - fastapi like functionality on top of Django Rest Framework</p>
</li>
<li><p>Django ORM now has <a target="_blank" href="https://docs.djangoproject.com/en/4.1/releases/4.1/#asynchronous-orm-interface">async</a> queryset method! While not fully <a target="_blank" href="https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/">red</a> yet, works are on the way to make everything async down to the database driver.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Buildless JavaScript]]></title><description><![CDATA[The title didn't catch my interest the first time I looked at it. I'm not working on any JavaScript project at the moment after all. But the second time made me curious. And I'm glad I clicked on the link.
So it talks about using JavaScript without a...]]></description><link>https://grep.koditi.my/buildless-javascript</link><guid isPermaLink="true">https://grep.koditi.my/buildless-javascript</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Tailwind CSS]]></category><category><![CDATA[Vue.js]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Sat, 18 Feb 2023 23:01:18 GMT</pubDate><content:encoded><![CDATA[<p>The title didn't catch my interest the first time I looked at it. I'm not working on any JavaScript project at the moment after all. But the second time made me curious. And I'm glad I clicked on the <a target="_blank" href="https://blog.6nok.org/buildless-javascript/">link</a>.</p>
<p>So it talks about using <a target="_blank" href="https://lobste.rs/s/0jzgzb/writing_javascript_without_build_system">JavaScript without any build system</a>. Aaah, a topic that is close to my heart. Ever remembered the days when you can just add some <code>&lt;script src="..."</code> tag in your HTML and call it a day?</p>
<p>I'll just list a few things that are interesting to me here:-</p>
<ul>
<li><p>Julia's Vue <a target="_blank" href="https://github.com/jvns/vue3-tiny-template">project template</a> - it just index.html + script.js</p>
</li>
<li><p><a target="_blank" href="https://twind.dev/">Twind</a> - I'm excited about this one. Always wish I can just use tailwindcss by simply adding the <code>&lt;script src=</code> tag. This makes it possible. It's a compiler that will compile tailwind utility class into CSS on the fly.</p>
</li>
<li><p>Fresh - Deno web framework. TIL that Deno is not fond of a build system.</p>
</li>
<li><p>Supabase - ok, this one is not really related to the topic but I saw it on the Fresh chat demo. It claims to be an open-source Firebase alternative. Basically, it gave you hosted PostgreSQL database with a nice Firebase-like API to access it.</p>
</li>
<li><p><a target="_blank" href="https://htmx.org/essays/no-build-step/">HTMX</a> no build step.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Testing Pyramid app - Changing settings for function under test]]></title><description><![CDATA[Today I was fixing one of our Pyramid app's test. Due to some changes in business logic, I need to change some settings for this particular test to pass. Should be a straightforward fix.
We're using pytest to run the tests and our conftest.py resembl...]]></description><link>https://grep.koditi.my/testing-pyramid-app-changing-settings-for-function-under-test</link><guid isPermaLink="true">https://grep.koditi.my/testing-pyramid-app-changing-settings-for-function-under-test</guid><category><![CDATA[DebuggingFeb]]></category><category><![CDATA[Python]]></category><category><![CDATA[celery]]></category><category><![CDATA[Pyramid]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Thu, 16 Feb 2023 12:29:57 GMT</pubDate><content:encoded><![CDATA[<p>Today I was fixing one of our Pyramid app's test. Due to some changes in business logic, I need to change some settings for this particular test to pass. Should be a straightforward fix.</p>
<p>We're using pytest to run the tests and our <code>conftest.py</code> resembles what is mentioned in this Pyramid testing <a target="_blank" href="https://docs.pylonsproject.org/projects/pyramid/en/latest/tutorials/wiki2/tests.html">documentation</a>. For example, we have this fixture defined in the file:-</p>
<pre><code class="lang-python"><span class="hljs-meta">@pytest.fixture(scope="session")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">app_env</span>(<span class="hljs-params">ini_path: str</span>) -&gt; AppEnvType:</span>
    <span class="hljs-string">"""Initialize WSGI application from INI file given on the command line."""</span>
    env = bootstrap(ini_path)

    <span class="hljs-comment"># build schema</span>
    alembic_cfg = Config(<span class="hljs-string">"etc/alembic.ini"</span>)
    command.upgrade(alembic_cfg, <span class="hljs-string">"head"</span>)
    <span class="hljs-keyword">return</span> env
</code></pre>
<p>This mean, in our test we just need to request this fixture and manipulate it before executing the function under tests.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_something</span>(<span class="hljs-params">app_env, paramiko: mock.MagicMock,
                   celery_db: Session, demo_celery: None, ....</span>):</span>
    app_env[<span class="hljs-string">"registry"</span>].settings[<span class="hljs-string">"special_config"</span>] = <span class="hljs-literal">False</span>
    <span class="hljs-keyword">with</span> pytest.raises(
        ValueError, match=<span class="hljs-string">r"not enough values to unpack \(expected 2, got 1\)"</span>
    ):
        func_to_test()
</code></pre>
<p>But that <code>special_config</code> flag never set to False. Going through the documentation again, I'm pretty sure that's all I need to manipulate the settings before running my function under tests. But it looks like the settings are coming from somewhere else and not from my fixture.</p>
<p>After hours of pulling my hair, I did what those in desperation usually did. I start randomly removing stuff to see what could break. I removed <code>celery_db</code> and <code>demo_celery</code> from the fixture request. And something interesting happened. The test failed because of missing stuff.</p>
<pre><code class="lang-python">src/kai/article/tests/test_article_tasks.py:<span class="hljs-number">586</span>: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
.venv/lib/python3<span class="hljs-number">.9</span>/site-packages/celery/local.py:<span class="hljs-number">191</span>: <span class="hljs-keyword">in</span> __call__
    <span class="hljs-keyword">return</span> self._get_current_object()(*a, **kw)
src/kai/tasks/__init__.py:<span class="hljs-number">112</span>: <span class="hljs-keyword">in</span> __call__
    self.settings = app.conf[<span class="hljs-string">"settings"</span>]
.venv/lib/python3<span class="hljs-number">.9</span>/site-packages/celery/utils/collections.py:<span class="hljs-number">449</span>: <span class="hljs-keyword">in</span> __getitem__
    <span class="hljs-keyword">return</span> self.__missing__(key)
</code></pre>
<p>That <code>app.conf</code> things caught my eye. I started tracing where that <code>app</code> comes from. As we can see in the snippet above, it is being used in the modules <code>src/kai/tasks/__init__.py</code> I can see in the modules:-</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> kai.celeryapp <span class="hljs-keyword">import</span> app
</code></pre>
<p>And in <code>src/kai/celeryapp.py</code>:-</p>
<pre><code class="lang-python">app = Celery()
app.user_options[<span class="hljs-string">"preload"</span>].add(add_preload_arguments)
app.steps[<span class="hljs-string">"worker"</span>].add(StructLogInitStep)

<span class="hljs-comment"># Plaster parts</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setup</span>(<span class="hljs-params">ini_path: str</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
    <span class="hljs-string">"""Given ini file, load settings and setup Celery."""</span>
    loader = plaster.get_loader(ini_path)
    settings = loader.get_settings(<span class="hljs-string">"celery"</span>)
    <span class="hljs-comment"># <span class="hljs-doctag">TODO:</span> this line can fail, but celery will swallow exception</span>
    resolver = DottedNameResolver()
    celery = resolver.resolve(settings.get(<span class="hljs-string">"use"</span>))

    celery(app, loader)
</code></pre>
<p>So that's it! The function under tests is a celery task and it turns out that the celery task, is using a separate <code>app</code> instance, different from the main app. That's why my changes to the settings in the registry have no effect at all. It uses a different registry.</p>
<p>Knowing this, the fix to my test is simply:-</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_something</span>(<span class="hljs-params">paramiko: mock.MagicMock,
                   celery_db: Session, demo_celery: None, ....</span>):</span>
    <span class="hljs-keyword">from</span> kai.celeryapp <span class="hljs-keyword">import</span> app
    app.conf[<span class="hljs-string">"settings"</span>][<span class="hljs-string">"special_config"</span>] = <span class="hljs-literal">False</span>
    <span class="hljs-keyword">with</span> pytest.raises(
        ValueError, match=<span class="hljs-string">r"not enough values to unpack \(expected 2, got 1\)"</span>
    ):
        func_to_test()
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Django: Things you don't learn from the tutorials]]></title><description><![CDATA[Let's say you want to add RSS feed to your blog built using Django. Django has syndication feed framework so that's probably the one that you'll use.
I'll just take the code example from the documentation linked above. Your feed's code will look like...]]></description><link>https://grep.koditi.my/django-things-you-dont-learn-from-the-tutorials</link><guid isPermaLink="true">https://grep.koditi.my/django-things-you-dont-learn-from-the-tutorials</guid><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Fri, 10 Feb 2023 23:29:25 GMT</pubDate><content:encoded><![CDATA[<p>Let's say you want to add RSS feed to your blog built using Django. Django has <a target="_blank" href="https://docs.djangoproject.com/en/4.1/ref/contrib/syndication/">syndication feed framework</a> so that's probably the one that you'll use.</p>
<p>I'll just take the code example from the documentation linked above. Your feed's code will look like this:-</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib.syndication.views <span class="hljs-keyword">import</span> Feed
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> reverse
<span class="hljs-keyword">from</span> policebeat.models <span class="hljs-keyword">import</span> NewsItem

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LatestEntriesFeed</span>(<span class="hljs-params">Feed</span>):</span>
    title = <span class="hljs-string">"Police beat site news"</span>
    link = <span class="hljs-string">"/sitenews/"</span>
    description = <span class="hljs-string">"Updates on changes and additions to police beat central."</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">items</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> NewsItem.objects.order_by(<span class="hljs-string">'-pub_date'</span>)[:<span class="hljs-number">5</span>]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">item_title</span>(<span class="hljs-params">self, item</span>):</span>
        <span class="hljs-keyword">return</span> item.title

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">item_description</span>(<span class="hljs-params">self, item</span>):</span>
        <span class="hljs-keyword">return</span> item.description

    <span class="hljs-comment"># item_link is only needed if NewsItem has no get_absolute_url method.</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">item_link</span>(<span class="hljs-params">self, item</span>):</span>
        <span class="hljs-keyword">return</span> reverse(<span class="hljs-string">'news-item'</span>, args=[item.pk])
</code></pre>
<p>And will hook that feed class into <code>urls.py</code> like this:-</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path
<span class="hljs-keyword">from</span> myproject.feeds <span class="hljs-keyword">import</span> LatestEntriesFeed

urlpatterns = [
    <span class="hljs-comment"># ...</span>
    path(<span class="hljs-string">'latest/feed/'</span>, LatestEntriesFeed()),
    <span class="hljs-comment"># ...</span>
]
</code></pre>
<p>All is well and good until there's a new requirement that the feeds listed should be based on the user's current language. So how do we know what is the user's current language?</p>
<p>User's language can be inferred from the <code>Request</code> object as <code>request.LANGUAGE_CODE</code>. But no where in code above we can see a <code>request</code> object.</p>
<p>Usually, we will see the request object when we implement the views function, such as in the code below:-</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.http <span class="hljs-keyword">import</span> HttpResponse

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">show_feed</span>(<span class="hljs-params">request</span>):</span>
    <span class="hljs-keyword">return</span> HttpResponse(<span class="hljs-string">"Hello world"</span>)
</code></pre>
<p>I'm just too lazy to show the example above <a target="_blank" href="https://spookylukey.github.io/django-views-the-right-way/">as CBV</a>.</p>
<p>But our feed's code above looks nothing like views, be it a CBV or FBV. Depending on your knowledge and understanding of Django, you might see a hint that the above code is also a views or you will have no idea at all.</p>
<p>The most obvious hint would be the code in the <code>urls.py</code> where we hook up our Feed class to a url path. Let's see it again:-</p>
<pre><code class="lang-python">urlpatterns = [
    <span class="hljs-comment"># ...</span>
    path(<span class="hljs-string">'latest/feed/'</span>, LatestEntriesFeed()),   <span class="hljs-comment"># ...</span>
]
</code></pre>
<p>The second parameter to the <code>path()</code> function must be a views (or a urls include but let's save that for the other day). So <code>LatestEntriesFeed()</code> will act as a views. How can that be? We defined it as a class of <code>Feed</code>.</p>
<p>Some might be confused that we're passing class <code>LatestEntriesFeed</code> to the <code>path()</code> function. This is a common mistake that I observed among beginners or junior developers. The parentheses at the end make a difference.</p>
<pre><code class="lang-python">path(<span class="hljs-string">'latest/feed/'</span>, LatestEntriesFeed),
</code></pre>
<p>This means you're passing a class to the path function. While this:-</p>
<pre><code class="lang-python">path(<span class="hljs-string">'latest/feed/'</span>, LatestEntriesFeed()),
</code></pre>
<p>You're passing an <strong>instance</strong> of that class. Writing it like the below maybe will make it much clearer:-</p>
<pre><code class="lang-python">feed_views = LatestEntriesFeed()
urlpatterns = [
    <span class="hljs-comment"># ...</span>
    path(<span class="hljs-string">'latest/feed/'</span>, feed_views),   <span class="hljs-comment"># ...</span>
]
</code></pre>
<p><mark>NOTES:-</mark></p>
<p><mark>While reading the source code for django syndication framework, I noticed the use of </mark> <code>get_language()</code> <mark> function from </mark> <code>django.utils.translation</code><mark>. So the rest of this article is just purely an academic exercise ... :(</mark></p>
<p>There are 2 ways we can figure out where the <code>request</code> object exists. First is by using <code>breakpoint()</code> to step up the caller of our method and see at which point <code>request</code> object exists. The second is by reading the source code.</p>
<p>Let us take a look at the first method. Put a breakpoint in our <code>items()</code> method.</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LatestEntriesFeed</span>(<span class="hljs-params">Feed</span>):</span>
    title = <span class="hljs-string">"Police beat site news"</span>
    link = <span class="hljs-string">"/sitenews/"</span>
    description = <span class="hljs-string">"Updates on changes and additions to police beat central."</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">items</span>(<span class="hljs-params">self</span>):</span>
        breakpoint()
        <span class="hljs-keyword">return</span> NewsItem.objects.order_by(<span class="hljs-string">'-pub_date'</span>)[:<span class="hljs-number">5</span>]
</code></pre>
<p>When we make a request again, we should get something like this on our console:-</p>
<pre><code class="lang-python">&gt; /workspace/kai-site/src/kai_site/feeds.py(<span class="hljs-number">14</span>)items()
-&gt; <span class="hljs-keyword">return</span> BlogPage.objects.live().order_by(<span class="hljs-string">'-date'</span>)
(Pdb) l
  <span class="hljs-number">9</span>        
 <span class="hljs-number">12</span>         <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">items</span>(<span class="hljs-params">self, request</span>):</span>
 <span class="hljs-number">13</span>             breakpoint()
 <span class="hljs-number">14</span>  -&gt;         <span class="hljs-keyword">return</span> BlogPage.objects.live().order_by(<span class="hljs-string">'-date'</span>)
 <span class="hljs-number">15</span>  
 <span class="hljs-number">16</span>         <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">item_title</span>(<span class="hljs-params">self, item</span>):</span>
 <span class="hljs-number">17</span>             <span class="hljs-keyword">return</span> item.title
 <span class="hljs-number">18</span>  
 <span class="hljs-number">19</span>         <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">item_description</span>(<span class="hljs-params">self, item</span>):</span>
</code></pre>
<p>Step one level up will give us this:-</p>
<pre><code class="lang-python">(Pdb) u
&gt; /workspace/.pyenv_mirror/poetry/virtualenvs/kai-site-gqAITAgE-py3<span class="hljs-number">.10</span>/lib/python3<span class="hljs-number">.10</span>/site-packages/django/contrib/syndication/views.py(<span class="hljs-number">103</span>)_get_dynamic_attr()
-&gt; <span class="hljs-keyword">return</span> attr(obj)
</code></pre>
<p>Step up again and we will see this:-</p>
<pre><code class="lang-python">-&gt; <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> self._get_dynamic_attr(<span class="hljs-string">"items"</span>, obj):
(Pdb) l
<span class="hljs-number">175</span>                 <span class="hljs-keyword">try</span>:
<span class="hljs-number">176</span>                     description_tmp = loader.get_template(self.description_template)
<span class="hljs-number">177</span>                 <span class="hljs-keyword">except</span> TemplateDoesNotExist:
<span class="hljs-number">178</span>                     <span class="hljs-keyword">pass</span>
<span class="hljs-number">179</span>  
<span class="hljs-number">180</span>  -&gt;         <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> self._get_dynamic_attr(<span class="hljs-string">"items"</span>, obj):
<span class="hljs-number">181</span>                 context = self.get_context_data(
<span class="hljs-number">182</span>                     item=item, site=current_site, obj=obj, request=request
<span class="hljs-number">183</span>                 )
<span class="hljs-number">184</span>                 <span class="hljs-keyword">if</span> title_tmp <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
<span class="hljs-number">185</span>                     title = title_tmp.render(context, request)
</code></pre>
<p>That's it! We can see the <code>request</code> object. So our <code>Feed</code> class does has the <code>request</code> object somewhere but it's not available to our method.</p>
<p>If the Feed class is implemented as CBV, then our instance's <code>self</code> will have the <code>request</code> object. This syndication framework I guess was implemented before the CBV implementation. The pattern used here is one of the <a target="_blank" href="https://code.djangoproject.com/wiki/ClassBasedViews">proposed CBV implementation</a> but as history told us, the class method approach is finally the one that gets implemented.</p>
<p>Btw, let's get back to the question of how our feed class can act like a Views. If you look into the source code of the syndication framework, we can see this line of code:-</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Feed</span>:</span>
    feed_type = feedgenerator.DefaultFeed
    title_template = <span class="hljs-literal">None</span>
    description_template = <span class="hljs-literal">None</span>
    language = <span class="hljs-literal">None</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__call__</span>(<span class="hljs-params">self, request, *args, **kwargs</span>):</span>
        <span class="hljs-keyword">try</span>:
            obj = self.get_object(request, *args, **kwargs)
        <span class="hljs-keyword">except</span> ObjectDoesNotExist:
            <span class="hljs-keyword">raise</span> Http404(<span class="hljs-string">"Feed object does not exist."</span>)
        feedgen = self.get_feed(obj, request)
        response = HttpResponse(content_type=feedgen.content_type)
        <span class="hljs-keyword">if</span> hasattr(self, <span class="hljs-string">"item_pubdate"</span>) <span class="hljs-keyword">or</span> hasattr(self, <span class="hljs-string">"item_updateddate"</span>):
            <span class="hljs-comment"># if item_pubdate or item_updateddate is defined for the feed, set</span>
            <span class="hljs-comment"># header so as ConditionalGetMiddleware is able to send 304 NOT MODIFIED</span>
            response.headers[<span class="hljs-string">"Last-Modified"</span>] = http_date(
                feedgen.latest_post_date().timestamp()
            )
        feedgen.write(response, <span class="hljs-string">"utf-8"</span>)
        <span class="hljs-keyword">return</span> response
</code></pre>
<p>The class implemented the magic method <code>__call__</code> which will turn an instance of that class into a <em>callable</em>. In Python, a callable is an object that we can call (doh ...). The most common callable is a function. A class is also callable. Calling a class will return an instance of that class.</p>
<p>Now let's get into the actual question we have - how do we get the current language from our <code>items()</code> method? By default, the <code>items()</code> method get called without any parameter. But if we add a second parameter to the method, it will pass any object returned by the <code>get_object()</code> method. The default implementation in the Feed class only returns None so we need to override the method.</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LatestEntriesFeed</span>(<span class="hljs-params">Feed</span>):</span>
    title = <span class="hljs-string">"Police beat site news"</span>
    link = <span class="hljs-string">"/sitenews/"</span>
    description = <span class="hljs-string">"Updates on changes and additions to police beat central."</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">items</span>(<span class="hljs-params">self, request</span>):</span>
        language = request.LANGUAGE_CODE
        <span class="hljs-keyword">return</span> (NewsItem.objects.filter(language=language)
                .order_by(<span class="hljs-string">'-pub_date'</span>)[:<span class="hljs-number">5</span>])

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">item_title</span>(<span class="hljs-params">self, item</span>):</span>
        <span class="hljs-keyword">return</span> item.title

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_object</span>(<span class="hljs-params">self, request, *args, **kwargs</span>):</span>
        <span class="hljs-keyword">return</span> request
</code></pre>
<p>As we can see above, <code>get_object()</code> get passed a <code>request</code> object. The actual purpose of this method is to return our Feed object but since we don't have any, we just return the <code>request</code> object. The object then will get passed to <code>items()</code> the method as second parameter. If we do have a feed object to return, then can attach the request object to that object instead.</p>
]]></content:encoded></item><item><title><![CDATA[Wagtail finding url name for submitting new translation]]></title><description><![CDATA[We're adding Translations tab to the wagtail admin edit pages. Something like this:-

And if there's no translation yet, add link for creating new translation like this:-

So I was scouring wagtail github repo looking for the url name to be used in t...]]></description><link>https://grep.koditi.my/wagtail-finding-url-name-for-submitting-new-translation</link><guid isPermaLink="true">https://grep.koditi.my/wagtail-finding-url-name-for-submitting-new-translation</guid><category><![CDATA[Django]]></category><category><![CDATA[wagtail]]></category><category><![CDATA[Python]]></category><category><![CDATA[i18n]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Tue, 17 Jan 2023 09:28:28 GMT</pubDate><content:encoded><![CDATA[<p>We're adding Translations tab to the wagtail admin edit pages. Something like this:-</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673946914399/db2d0b07-2394-44bf-813b-3d59079cecbc.png" alt class="image--center mx-auto" /></p>
<p>And if there's no translation yet, add link for creating new translation like this:-</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673946963035/b809b6f0-4161-4819-acd3-6fad9ff434ad.png" alt class="image--center mx-auto" /></p>
<p>So I was scouring wagtail github repo looking for the url name to be used in the template. The url name for showing the translated version is easy, since it just an edit page. We can find it in <code>wagtail/admin/urls/pages.py</code> through the EditView class.</p>
<p>The url to submit for new translation however can't be found anywhere. Then it comes to my mind maybe it's not implemented in wagtail but in the wagtail-localize app. I looked into that app repo but can't find anything as well. Btw you can find urls for wagtail-localize app in <code>wagtail_localize/wagtail_hooks.py</code>. You can thank me later.</p>
<p>Fortunately I remembered that we can list all urls through <code>django-extensions</code> show_urls command.</p>
<pre><code class="lang-python">poetry run python manage.py show_urls
</code></pre>
<p>That will list all urls defined in all installed app. You will see something like this:-</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673948593225/c9d474a7-820c-42d1-92c9-618bbf5fe2c3.png" alt class="image--center mx-auto" /></p>
<p>Turn out the urls is define in <code>simple_translation</code> app. The templates then looks like this:-</p>
<pre><code class="lang-python">&lt;fieldset&gt;
    {% <span class="hljs-keyword">if</span> self.heading %}
        &lt;legend <span class="hljs-class"><span class="hljs-keyword">class</span>="<span class="hljs-title">w</span>-<span class="hljs-title">sr</span>-<span class="hljs-title">only</span>"&gt;{{ <span class="hljs-title">self</span>.<span class="hljs-title">heading</span> }}&lt;/<span class="hljs-title">legend</span>&gt;
    {% <span class="hljs-title">endif</span> %}
    &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="{{ <span class="hljs-title">self</span>.<span class="hljs-title">classname</span> }}"&gt;
    {% <span class="hljs-title">for</span> <span class="hljs-title">translation</span> <span class="hljs-title">in</span> <span class="hljs-title">self</span>.<span class="hljs-title">instance</span>.<span class="hljs-title">get_translations</span> %}
    &lt;<span class="hljs-title">li</span>&gt;&lt;<span class="hljs-title">a</span> <span class="hljs-title">href</span>="{% <span class="hljs-title">url</span> '<span class="hljs-title">wagtailadmin_pages</span>:</span>edit<span class="hljs-string">' translation.id %}"&gt;{{ translation.title }}&lt;/a&gt;&lt;/li&gt;
    {% empty %}
    &lt;a href="{% url '</span>simple_translation:submit_page_translation<span class="hljs-string">' self.instance.id %}"&gt;Add New Translation&lt;/a&gt;
    {% endfor %}
    &lt;/div&gt;
&lt;/fieldset&gt;</span>
</code></pre>
<p>On how to add custom tab like above, I guess that would be for another post ;)</p>
]]></content:encoded></item><item><title><![CDATA[Wagtail preview post 400 error]]></title><description><![CDATA[Since there were a number of errors found when searching for this but I can't find one that mentioned this solution, I would put the notes here, hopefully, Google will catch this up as well ;)
In our case, it was the Site settings not properly config...]]></description><link>https://grep.koditi.my/wagtail-preview-post-400-error</link><guid isPermaLink="true">https://grep.koditi.my/wagtail-preview-post-400-error</guid><category><![CDATA[wagtail]]></category><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><category><![CDATA[cms]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Fri, 06 Jan 2023 02:48:33 GMT</pubDate><content:encoded><![CDATA[<p>Since there were a number of errors found when searching for this but I can't find one that mentioned this solution, I would put the notes here, hopefully, Google will catch this up as well ;)</p>
<p>In our case, it was the Site settings not properly configured. It looked like this when the error appeared:-</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672973140737/de0087a9-f6ba-4f02-b3c6-b62d11870c0b.png" alt class="image--center mx-auto" /></p>
<p>You need to change that localhost to your actual domain such as yoursite.com and the Preview would then works.</p>
]]></content:encoded></item><item><title><![CDATA[Wagtail get parent page gotchas (tldr; use specific property)]]></title><description><![CDATA[I was implementing a tag page, a page for listing blog entries for a particular tag using RoutablePageMixin.
class BlogIndexPage(RoutablePageMixin, Page):
    intro = models.CharField(max_length=250)
    content_panels = Page.content_panels + [
     ...]]></description><link>https://grep.koditi.my/wagtail-get-parent-page-gotchas-tldr-use-specific-property</link><guid isPermaLink="true">https://grep.koditi.my/wagtail-get-parent-page-gotchas-tldr-use-specific-property</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><category><![CDATA[wagtail]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Wed, 28 Dec 2022 13:34:32 GMT</pubDate><content:encoded><![CDATA[<p>I was implementing a tag page, a page for listing blog entries for a particular tag using <code>RoutablePageMixin</code>.</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BlogIndexPage</span>(<span class="hljs-params">RoutablePageMixin, Page</span>):</span>
    intro = models.CharField(max_length=<span class="hljs-number">250</span>)
    content_panels = Page.content_panels + [
        FieldPanel(<span class="hljs-string">'intro'</span>)
    ]

<span class="hljs-meta">    @path("t/&lt;str:tag&gt;/", name="blog_tag")</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">blog_tag</span>(<span class="hljs-params">self, request, tag</span>):</span>
        blogpages = BlogPage.objects.child_of(self).live()
        blogpages = blogpages.filter(tags__name=tag)
        <span class="hljs-keyword">return</span> self.render(
            request,
            context_overrides={
                <span class="hljs-string">"blogpages"</span>: blogpages,
            },
            template=<span class="hljs-string">"kai_site/blog_tag_index_page.html"</span>
        )
</code></pre>
<p>And then in the blog page views templates we will list all tags the page is associated with:-</p>
<pre><code class="lang-xml">            ...
            {% if page.tags.all.count %}
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"tags"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Tags<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                {% for tag in page.tags.all %}
                    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{% routablepageurl blog_index url_name='blog_tag' tag=tag %}"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>&gt;</span>{{ tag }}<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
                {% endfor %}
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            {% endif %}
        <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
</code></pre>
<p>And <code>blog_index</code> is defined as:-</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BlogPage</span>(<span class="hljs-params">Page</span>):</span>
    date = models.DateField(<span class="hljs-string">"Post date"</span>)
    intro = models.CharField(max_length=<span class="hljs-number">250</span>)
    tags = ClusterTaggableManager(through=BlogPageTag, blank=<span class="hljs-literal">True</span>)
    ...
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_context</span>(<span class="hljs-params">self, request</span>):</span>
        context = super().get_context(request)
        context[<span class="hljs-string">"blog_index"</span>] = self.get_parent().specific
        <span class="hljs-keyword">return</span> context
</code></pre>
<p>However I got the following error:-</p>
<pre><code class="lang-python">File <span class="hljs-string">"/workspace/.pyenv_mirror/poetry/virtualenvs/kai-site-gqAITAgE-py3.10/lib/python3.10/site-packages/django/template/library.py"</span>, line <span class="hljs-number">237</span>, <span class="hljs-keyword">in</span> render
    output = self.func(*resolved_args, **resolved_kwargs)
  File <span class="hljs-string">"/workspace/.pyenv_mirror/poetry/virtualenvs/kai-site-gqAITAgE-py3.10/lib/python3.10/site-packages/wagtail/contrib/routable_page/templatetags/wagtailroutablepage_tags.py"</span>, line <span class="hljs-number">25</span>, <span class="hljs-keyword">in</span> routablepageurl
    routed_url = page.reverse_subpage(url_name, args=args, kwargs=kwargs)
AttributeError: <span class="hljs-string">'Page'</span> object has no attribute <span class="hljs-string">'reverse_subpage'</span>
</code></pre>
<p>Checking what the <code>page</code> instance is shows that:-</p>
<pre><code class="lang-python"><span class="hljs-meta">&gt;&gt;&gt; </span>type(page)
&lt;<span class="hljs-class"><span class="hljs-keyword">class</span> '<span class="hljs-title">wagtail</span>.<span class="hljs-title">models</span>.<span class="hljs-title">Page</span>'&gt;
&gt;&gt;&gt; <span class="hljs-title">page</span>.<span class="hljs-title">__class__</span>
&lt;<span class="hljs-title">class</span> '<span class="hljs-title">wagtail</span>.<span class="hljs-title">models</span>.<span class="hljs-title">Page</span>'&gt;</span>
</code></pre>
<p>So I'm getting the base class, not my subclass <code>BlogIndexPage</code>. Took some time debugging with me getting off-tangent thinking that the blog page was created with the wrong parent. But peeking into the database shows everything is in order.</p>
<p>It only after looking into wagtail source code for the Page models I found this:-</p>
<pre><code class="lang-python">    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_specific</span>(<span class="hljs-params">self, deferred=False, copy_attrs=None, copy_attrs_exclude=None</span>):</span>
        <span class="hljs-string">"""
        Return this page in its most specific subclassed form.
        By default, a database query is made to fetch all field values for the
        specific object. If you only require access to custom methods or other
        non-field attributes on the specific object, you can use
        ``deferred=True`` to avoid this query. However, any attempts to access
        specific field values from the returned object will trigger additional
        database queries.</span>
</code></pre>
<p>So that's it!</p>
<pre><code class="lang-python"><span class="hljs-meta">&gt;&gt;&gt; </span>type(page)
&lt;<span class="hljs-class"><span class="hljs-keyword">class</span> '<span class="hljs-title">wagtail</span>.<span class="hljs-title">models</span>.<span class="hljs-title">Page</span>'&gt;
&gt;&gt;&gt; <span class="hljs-title">page</span>.<span class="hljs-title">__class__</span>
&lt;<span class="hljs-title">class</span> '<span class="hljs-title">wagtail</span>.<span class="hljs-title">models</span>.<span class="hljs-title">Page</span>'&gt;
&gt;&gt;&gt; <span class="hljs-title">page</span>.<span class="hljs-title">specific</span>
&lt;<span class="hljs-title">BlogIndexPage</span>:</span> Blog&gt;
</code></pre>
<p><code>Page.get_parent()</code> only return the instance with the base class as its type. We need to use the <code>.specific</code> property to get our sub-class as its type.</p>
]]></content:encoded></item><item><title><![CDATA[Flaw in using ssh key for authentication]]></title><description><![CDATA[Using ssh key for authentication has become standard practice in many organizations. It is considered much secure than using a password. While that is true, the flaw in using the ssh key is before we can actually use it for authentication. And this s...]]></description><link>https://grep.koditi.my/flaw-in-using-ssh-key-for-authentication</link><guid isPermaLink="true">https://grep.koditi.my/flaw-in-using-ssh-key-for-authentication</guid><category><![CDATA[Linux]]></category><category><![CDATA[ssh]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Tue, 20 Dec 2022 01:53:20 GMT</pubDate><content:encoded><![CDATA[<p>Using ssh key for authentication has become standard practice in many organizations. It is considered much secure than using a password. While that is true, the flaw in using the ssh key is before we can actually use it for authentication. And this seldom gets highlighted, leading to many IT professional don’t even realize that this is a problem.</p>
<p>Have ever bumped into this when you try to ssh into a new host first time?</p>
<pre><code class="lang-bash">$ ssh kamal@51.251.110.111
The authenticity of host <span class="hljs-string">'[51.251.110.111] ([51.251.110.111]:22)'</span> can<span class="hljs-string">'t be established.
ECDSA key fingerprint is SHA256:xouheGRcbdddfdfdfaaaaaaaaaaaaacxxxxxxcadd*
*Are you sure you want to continue connecting (yes/no/[fingerprint])? yes*</span>
</code></pre>
<p>In a lot of tutorials around the net, usually, you’ll just be told to type yes, and that’s where all the problems begin.</p>
<p>Unlike TLS/SSL which certificate that requires third party (CA) to prove its validity, SSH doesn’t use any third party to prove the authenticity of the key. Instead, they employ a mechanism called Trust on First Use or ToFU. And that’s what the prompt you see above.</p>
<p>For an ssh connection to establish, it must first make sure 2 things are correct:-</p>
<ol>
<li><p>The key that the user uses to access the server is correct and allowed - this is basically by adding the public key into the user’s authorized_key file on the server.</p>
</li>
<li><p>The client can verify the identity of the server is correct. That’s what the prompt above is. It asks you to verify that you trust that indeed the server that you want to log in.</p>
</li>
</ol>
<p>To verify that, you have to take the server key fingerprint (shown in the prompt) and then compared it with the key that you know belongs to the server. Only if you have verified that it matched, then you should type “yes”.</p>
<p>In the old days, that was not a problem. Usually, you will set up a server in the data center by being present physically in the data center. After setting up the server, you can run a command to see what is its key fingerprint, write it down and go back to the office. Then you will try to ssh first time to the server from your office’s computer. You got the same prompt as above, so you compare the fingerprint presented to the fingerprint that you wrote down on paper while in the data center. It matched and now you have a secure connection from your office to the server in the data center.</p>
<p>But today in all cloud environments, we are no longer present physically in the data center to copy the server’s key fingerprint. Some cloud providers will tell you the fingerprint when you provision the instance in their management console. This is the most ideal situation, assuming we already access their management console securely, with HTTPS for example.</p>
<p>Another mechanism is by providing virtual console access (similar to you plugging the keyboard and monitor into the server) so can you check the fingerprint yourself. But do not mistake this for the web terminal that some cloud provides, most of them just ssh clients, which means you still need to authenticate via ssh, which brings us back to the original problem.</p>
<p>AWS however doesn’t provide such an ideal solution. The only way we can discover the fingerprint is by inspecting the launch log, or the boot log, I’m not sure. But it is such a hassle that people simply type yes when prompted to verify the server fingerprint.</p>
<p>Now we have talked about the problem, I’ll just link to <a target="_blank" href="https://goteleport.com/blog/how-to-ssh-properly/">this</a> article that proposes the use of a certificate that will be verified by CA instead to replace the old key authentication.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Django translation mechanism]]></title><description><![CDATA[Let's try to understand django translation mechanism. Firstly, django uses a widely used mechanism in most open-source software, which is gettext, so it's not unique to django alone. Many other open-source software also using gettext as their transla...]]></description><link>https://grep.koditi.my/understanding-django-translation-mechanism</link><guid isPermaLink="true">https://grep.koditi.my/understanding-django-translation-mechanism</guid><category><![CDATA[Django]]></category><category><![CDATA[i18n]]></category><category><![CDATA[translation]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Mon, 19 Dec 2022 01:12:21 GMT</pubDate><content:encoded><![CDATA[<p>Let's try to understand django translation mechanism. Firstly, django uses a widely used mechanism in most open-source software, which is gettext, so it's not unique to django alone. Many other open-source software also using gettext as their translation machinery. The difference is just the tooling you build around it. Tooling here means the script, the function name, and the step you formulate to get the translated text. Some are heavily automated, and some are quite manual.</p>
<p>The general ideas in gettext translation are:-</p>
<p>1. Mark the string as translatable. There are a lot of ways to accomplish this. In program code, the best approach is to use a function. Why? Because first, it is syntactically correct. Function can always return value so it makes perfect sense as you can return the translated string from the function. But if you want to be fancy, you can just use any marker and then run the pre-processing script on that code to generate the translation you want.</p>
<p>2. Parse the code and extract the string marked as translatable. In django, you can see it implemented <a target="_blank" href="https://github.com/django/django/blob/ab7a85ac297464df82d8363455609979ca3603db/django/core/management/commands/makemessages.py#L675">here</a>.</p>
<p>Basically, you just run this command:-</p>
<pre><code class="lang-bash">xgettext -d django -L Python -kugettext_lazy test_trans.py
</code></pre>
<p>where <code>test_trans.py</code> is the file you want to translate. <code>django.po</code> will be generated in the current directory.</p>
<p>3. Translate the message file and compile it into a message object to optimize looking for translated string later on.</p>
<p>Now, to really understand how it work in practice, save the guess work and get your hand dirty in the console:-</p>
<pre><code class="lang-bash">09:41:40 {master} ~/git/kai-app$ python manage.py shell
&gt;&gt;&gt; from django.utils.translation import gettext_lazy, gettext, activate
&gt;&gt;&gt; gettext(<span class="hljs-string">'About LaLoka Labs'</span>)
<span class="hljs-string">'About LaLoka Labs'</span>
&gt;&gt;&gt; activate(<span class="hljs-string">'ja'</span>)
&gt;&gt;&gt; gettext(<span class="hljs-string">'About LaLoka Labs'</span>)
<span class="hljs-string">'About LaLoka Labs'</span>
</code></pre>
<p>It's not translated, so what is the problem here? Aaah, we forgot to generate the message object.</p>
<pre><code class="lang-bash">python manage.py compilemessages
</code></pre>
<pre><code class="lang-bash">from django.utils.translation import gettext_lazy, gettext, activate &gt;&gt;&gt; gettext(<span class="hljs-string">'About LaLoka Labs'</span>)
<span class="hljs-string">'About LaLoka Labs'</span>
&gt;&gt;&gt; activate(<span class="hljs-string">'ja'</span>)
&gt;&gt;&gt; gettext(<span class="hljs-string">'About LaLoka Labs'</span>)
<span class="hljs-string">'LaLoka Labsについて'</span>
</code></pre>
<p>It's work!</p>
<p>Now, what is the difference between ugettext and ugettext_lazy ? The docs is here - <a target="_blank" href="https://docs.djangoproject.com/en/1.11/topics/i18n/translation/#lazy-translation">https://docs.djangoproject.com/en/4.1/topics/i18n/translation/#lazy-translation</a>.</p>
<p>It said:-</p>
<blockquote>
<p>Use the lazy versions of translation functions in <code>django.utils.translation</code> (easily recognizable by the <code>lazy</code> suffix in their names) to translate strings lazily – when the value is accessed rather than when they’re called.</p>
<p>These functions store a lazy reference to the string – not the actual translation. The translation itself will be done when the string is used in a string context, such as in template rendering.</p>
<p>This is essential when calls to these functions are located in code paths that are executed at module load time.</p>
</blockquote>
<p>Hmm, what does that mean?</p>
<p>If you try it in console, you'll see:-</p>
<pre><code class="lang-bash">&gt;&gt;&gt; gettext_lazy(<span class="hljs-string">'About LaLoka Labs'</span>)
&lt;django.utils.functional.lazy.&lt;locals&gt;.__proxy__ object at 0x7f9c34880c50&gt;
</code></pre>
<p>Notice the difference. Unlike the previous example, this one does not return the translated string.</p>
<p>So why do you need gettext_lazy over gettext?</p>
<p>If you go further down the docs, it shows an example of translating some model's attributes. You should already know that models definition is loaded once when you start django. If you use <code>gettext</code>, which means the string will get translated at that time. But what if you access models attribute at runtime (later on), in a different translation context, like changing from en to ja? You'll not get the translated ja string because it has already been translated before. So using gettext_lazy will ensure the string is translated when you see it, not when the program gets loaded into memory.</p>
<p>We have had some cases in the past where the translatable strings did not pick up by <code>makemessages.</code> Turns out someone aliased the <code>gettext_lazy</code> function to <code>gettext_lz</code>. If you look at the django source I linked above, <code>makemessages</code> run this command to extract the string:-</p>
<pre><code class="lang-python">        <span class="hljs-keyword">elif</span> self.domain == <span class="hljs-string">"django"</span>:
            args = [
                <span class="hljs-string">"xgettext"</span>,
                <span class="hljs-string">"-d"</span>,
                self.domain,
                <span class="hljs-string">"--language=Python"</span>,
                <span class="hljs-string">"--keyword=gettext_noop"</span>,
                <span class="hljs-string">"--keyword=gettext_lazy"</span>,
                <span class="hljs-string">"--keyword=ngettext_lazy:1,2"</span>,
                <span class="hljs-string">"--keyword=pgettext:1c,2"</span>,
                <span class="hljs-string">"--keyword=npgettext:1c,2,3"</span>,
                <span class="hljs-string">"--keyword=pgettext_lazy:1c,2"</span>,
                <span class="hljs-string">"--keyword=npgettext_lazy:1c,2,3"</span>,
                <span class="hljs-string">"--output=-"</span>,
            ]
</code></pre>
<p><code>_lz</code> is not defined anywhere there. <code>_</code> is recognized by default by python gettext module.</p>
<p>Some more examples to help you understand when to use <code>gettext</code> and <code>gettext_lazy</code>:-</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span>(<span class="hljs-params">models.Model</span>):</span>
    name = models.CharField(help_text=_lz(<span class="hljs-string">'This is the help text'</span>))

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">say_something</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> _(<span class="hljs-string">"Hello world"</span>)
</code></pre>
<p><code>name</code> above will be executed when django started, either through runserver or apache restart, so it needs to be lazy. <code>say_something()</code> only executed by user, for example when they call <code>person.say_something()</code> in console, so it doesn't need to be lazy.</p>
<p>Now, can anyone tell what this means (this is from an old django project <a target="_blank" href="http://settings.py">settings.py</a>):-</p>
<pre><code class="lang-python">gettext = <span class="hljs-keyword">lambda</span> s: s
LANGUAGES = (
    (<span class="hljs-string">'en'</span>, ugettext(<span class="hljs-string">'English'</span>)),
    (<span class="hljs-string">'ja'</span>, ugettext(<span class="hljs-string">'日本語'</span>)),
)
</code></pre>
<p>Some would answer that to avoid circular import since the translation module depends on <code>settings.py</code> so we can't import <code>gettext</code> from the settings module itself.</p>
<p>However, in latest version of django, this workaround is not needed anymore and <code>django.utils.translation</code> can be imported from <a target="_blank" href="http://settings.py">settings.py</a>. Here's what the comment says about the workaround:-</p>
<pre><code class="lang-python"><span class="hljs-comment"># Here be dragons, so a short explanation of the logic won't hurt:</span>
<span class="hljs-comment"># We are trying to solve two problems: (1) access settings, in particular</span>
<span class="hljs-comment"># settings.USE_I18N, as late as possible, so that modules can be imported</span>
<span class="hljs-comment"># without having to first configure Django, and (2) if some other code creates</span>
<span class="hljs-comment"># a reference to one of these functions, don't break that reference when we</span>
<span class="hljs-comment"># replace the functions with their real counterparts (once we do access the</span>
<span class="hljs-comment"># settings).</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Understanding distro's config mechanism]]></title><description><![CDATA[Ubuntu 20.04 latest ami seems to start using EC2 instance connect by default. And if you want to muck around with it, you can start from /lib/systemd/system/ssh and /lib/systemd/system/ssh.service.d/ec2-instance-connect.conf, not in /etc/ssh/sshd_con...]]></description><link>https://grep.koditi.my/understanding-distros-config-mechanism</link><guid isPermaLink="true">https://grep.koditi.my/understanding-distros-config-mechanism</guid><category><![CDATA[Linux]]></category><category><![CDATA[Ubuntu]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Fri, 16 Dec 2022 23:25:05 GMT</pubDate><content:encoded><![CDATA[<p>Ubuntu 20.04 latest ami seems to start using EC2 instance connect by default. And if you want to muck around with it, you can start from <code>/lib/systemd/system/ssh</code> and <code>/lib/systemd/system/ssh.service.d/ec2-instance-connect.conf</code>, not in <code>/etc/ssh/sshd_config</code>.</p>
<p>You can thank me later.</p>
<p>And if you just look at <code>/etc/systemd/system/sshd.service</code> you'll feel kind of magic as well as there's nothing there related to EIC. That is just a symlink to <code>/lib/systemd/system/ssh</code>, which has all the meat there.</p>
<p>Other than <code>sshd.service</code>, <code>syslog.service</code> also symlink to <code>/lib/systemd/system/rsyslog.service</code>.</p>
<p>Ok, so <code>/etc/systemd/system</code> is supposed to be the place where you want to override the system's service unit definition, which is usually in <code>/lib/systemd/system</code>. <code>/etc/systemd/system</code> will have precedence over <code>/lib/systemd/system</code>.</p>
<p>And the correct way to override is not by editing the <code>.service</code> file but instead by creating a directory called <code>servicename.service.d/</code> directory at the same level and include <code>*.conf</code> file in that directory. Within that <code>.conf</code> file you can override any individual service section attributes such as <code>ExecStart=</code>.</p>
<p>So for example in <code>/lib/systemd/system/ssh.service.d/ec2-instance-connect.conf</code>, <code>ExecStart</code> is overriden with this command instead:-</p>
<pre><code class="lang-bash">[Service]
ExecStart=
ExecStart=/usr/sbin/sshd -D -o <span class="hljs-string">"AuthorizedKeysCommand /usr/share/ec2-instance-connect/eic_run_authorized_keys %%u %%f"</span> -o <span class="hljs-string">"AuthorizedKeysCommandUser ec2-instance-connect"</span> <span class="hljs-variable">$SSHD_OPTS</span>
</code></pre>
<p>If you just look in <code>/etc/ssh/sshd_config</code> or <code>/etc/systemd/system/sshd.service</code>, you will feel like a fool because in sshd_config, <code>AuthorizedKeysCommand</code> is commented:-</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Expect .ssh/authorized_keys2 to be disregarded by default in future.#AuthorizedKeysFile     .ssh/authorized_keys .ssh/authorized_keys2</span>

<span class="hljs-comment">#AuthorizedPrincipalsFile none</span>

<span class="hljs-comment">#AuthorizedKeysCommand none#AuthorizedKeysCommandUser nobody</span>
</code></pre>
<p>and in /etc/systemd/system/sshd.service, ExecStart looks like this:-</p>
<pre><code class="lang-bash">ExecStart=/usr/sbin/sshd -D <span class="hljs-variable">$SSHD_OPTS</span>
</code></pre>
<p>Nothing in there suggested the use of EC2 Instance Connect. And systemd selling proposition is standardization 😊</p>
<p>Let me check how Amazon Linux does it. I think this is very Ubuntu/Debian specific.</p>
<p><code>ssh</code> provides <code>Include /etc/ssh/sshd_config.d/*.conf</code> as override mechanism but I think a common dilemma faced by package maintainers or distro builders is whether to use the program's specific mechanism or use the system's mechanism.</p>
<p>And I think it's clear package maintainers prefer system mechanisms.</p>
<p>Amazon linux simply use <code>sshd_config</code> file.</p>
<pre><code class="lang-bash">AuthorizedKeysCommand /opt/aws/bin/eic_run_authorized_keys %u %f AuthorizedKeysCommandUser ec2-instance-connect
</code></pre>
<p>Much clearer. no hidden magic.</p>
]]></content:encoded></item><item><title><![CDATA[Toolkit vs libraries]]></title><description><![CDATA[I want to test something with Django, when working on new blog post about project structure. So I went to my terminal and create a new directory to hold the new django project.
The command to start a new django project is django-admin startproject. B...]]></description><link>https://grep.koditi.my/toolkit-vs-libraries</link><guid isPermaLink="true">https://grep.koditi.my/toolkit-vs-libraries</guid><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><category><![CDATA[Libraries]]></category><category><![CDATA[tools]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Thu, 15 Dec 2022 03:26:47 GMT</pubDate><content:encoded><![CDATA[<p>I want to test something with Django, when working on new blog post about project structure. So I went to my terminal and create a new directory to hold the new django project.</p>
<p>The command to start a new django project is <code>django-admin startproject</code>. But I stumbled there because I don't have the command django-admin. This is one thing that always confuses the newbie and make them stuck.</p>
<p>I paused a bit to reflect on how an inexperienced Django developer would think in this situation. There are many ways to go from here and it might feel daunting to some people. This is because we're facing different levels of concepts here but to inexperienced developers, it is just one bag of confusing things.</p>
<p>Django here exists as both a toolkit (in form of the <code>django-admin</code> command) and libraries (when you do <code>import django ...</code> in your app).</p>
<p>One quick way is to create a new virtual environment in the current directory I'm working on.</p>
<pre><code class="lang-bash">python -mvenv venv
</code></pre>
<p>Then I can install django inside that newly created venv:-</p>
<pre><code class="lang-bash">venv/bin/pip install django
venv/bin/django-admin startproject
</code></pre>
<p>I will also continue to use that venv to run my django project:-</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> myproject
../venv/bin/python manage.py runserver
</code></pre>
<p>So both the tooling (django-admin) and the libraries (django) in the same virtualenv. There's also another way to go, by treating <code>django-admin</code> as toolkit and install it "globally" within my <code>$HOME</code> environment. For this purpose I usually use <code>pipx</code> to install all the common tools I'm using for development.</p>
<pre><code class="lang-bash">pipx install django
django-admin startproject myproject
python -mvenv venv
venv/bin/pip install django
<span class="hljs-built_in">cd</span> myproject
../venv/bin/python manage.py runserver
</code></pre>
<p>Above, I'm treating <code>django-admin</code> as toolkit and installing it to global <code>$HOME</code> so it is always available whenever I need it. But for <code>myproject</code> I'm installing a separate django in venv specific for that project.</p>
<p>So what else that I'm treating as toolkit?</p>
<ul>
<li><p>Black</p>
</li>
<li><p>Flake8</p>
</li>
<li><p>Pre-commit</p>
</li>
<li><p>...</p>
</li>
</ul>
<p>For futher reading, head ups to this <a target="_blank" href="https://stackoverflow.com/questions/3057526/framework-vs-toolkit-vs-library">stackoverflow question</a>.</p>
]]></content:encoded></item><item><title><![CDATA[AWS API Gateway gotchas]]></title><description><![CDATA[Looks like you can't get the raw request url or query parameters:-

Query parameters get in different order .
You can't pass parameters with same key to simulate arrays .
API Gateway already decoding the query params so if your params has some esoter...]]></description><link>https://grep.koditi.my/aws-api-gateway-gotchas</link><guid isPermaLink="true">https://grep.koditi.my/aws-api-gateway-gotchas</guid><category><![CDATA[AWS]]></category><category><![CDATA[APIs]]></category><category><![CDATA[http]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Mon, 11 Jan 2021 07:30:51 GMT</pubDate><content:encoded><![CDATA[<p>Looks like you can't get the raw request url or query parameters:-</p>
<ul>
<li><a target="_blank" href="https://mobile.twitter.com/yongfook/status/1348523844361809920">Query parameters get in different order</a> .</li>
<li><a target="_blank" href="https://stackoverflow.com/questions/49372722/how-to-get-aws-api-gateway-invoke-url-in-an-aws-lambda-function?noredirect=1&amp;lq=1">You can't pass parameters with same key to simulate arrays</a> .</li>
<li><a target="_blank" href="https://stackoverflow.com/questions/52777868/accessing-raw-url-using-aws-api-gateway">API Gateway already decoding the query params so if your params has some esoteric encoding, tough luck for you</a> .</li>
</ul>
<p>The suggested workaround seems if you need to access raw data, don't pass it as query params, put it in the request body instead.</p>
]]></content:encoded></item><item><title><![CDATA[Django TransactionTestCase and django-q sync]]></title><description><![CDATA[Django provides 2 base class that you can use to write tests for your django app - TestCase and TransactionTestCase. Most of the time TestCase is what you will be used. It's faster as it runs each test within a transaction and rollback the transactio...]]></description><link>https://grep.koditi.my/django-transactiontestcase-and-django-q-sync</link><guid isPermaLink="true">https://grep.koditi.my/django-transactiontestcase-and-django-q-sync</guid><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><category><![CDATA[queue]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Sat, 28 Nov 2020 22:57:41 GMT</pubDate><content:encoded><![CDATA[<p>Django provides 2 base class that you can use to write tests for your django app - TestCase and TransactionTestCase. Most of the time TestCase is what you will be used. It's faster as it runs each test within a transaction and rollback the transaction when the test is finished.</p>
<p>The problem is that, if your code under test also need to test the effect of using transaction then you can't use <code>TestCase</code>. Django provides <code>TransactionTestCase</code> for this purpose. In this class, each test will have database setup and teardown process - tables created and migrations runs at the beginning of the test and being dropped when the test finish. This is applied to each test case so you can imagine how slow it is.</p>
<p>For more information you can refer to the <a target="_blank" href="https://docs.djangoproject.com/en/3.1/topics/testing/tools/#transactiontestcase">documentation</a> page.</p>
<h2 id="django-q">django-q</h2>
<p>Django-q is a lightweight task queue that we usually use in our Django project. We prefer it compared to celery and it support a number of brokers such as redis, SQS, IronMQ and RDBMS (through Django ORM).</p>
<p>One feature django-q provides is the <code>sync</code> parameter when adding task to the queue. The parameter is meant to bypass the queue and immediately executing the task function, which is something you usually do in tests.</p>
<p>In the beginning the <code>sync</code> implementation is quite simple, basically just calling the task function, something like:-</p>
<pre><code><span class="hljs-selector-tag">if</span> <span class="hljs-selector-tag">sync</span>:
    <span class="hljs-selector-tag">run</span>(task_func, *args, **kwargs, ...)
</code></pre><p>But in recent implementation this has been changed so that it better simulates the environment when the task was executed within the queue framework. So the implementation is something likes:-</p>
<pre><code><span class="hljs-keyword">if</span> <span class="hljs-keyword">sync</span>:
    _<span class="hljs-keyword">sync</span>(task)
</code></pre><p>and the <code>_sync()</code> function <a target="_blank" href="https://github.com/Koed00/django-q/blob/13632da35fe26f4e4f9b1e072150cd69a4127fb7/django_q/tasks.py#L754">defined</a> as:-</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_sync</span>(<span class="hljs-params">pack</span>):</span>
    <span class="hljs-string">"""Simulate a package travelling through the cluster."""</span>
    <span class="hljs-keyword">from</span> django_q.cluster <span class="hljs-keyword">import</span> worker, monitor

    task_queue = Queue()
    result_queue = Queue()
    task = SignedPackage.loads(pack)
    task_queue.put(task)
    task_queue.put(<span class="hljs-string">"STOP"</span>)
    worker(task_queue, result_queue, Value(<span class="hljs-string">"f"</span>, <span class="hljs-number">-1</span>))
    result_queue.put(<span class="hljs-string">"STOP"</span>)
    monitor(result_queue)
    task_queue.close()
    task_queue.join_thread()
    result_queue.close()
    result_queue.join_thread()
    <span class="hljs-keyword">return</span> task[<span class="hljs-string">"id"</span>]
</code></pre><p>So in the original implementation the task function simply executed, no different than just calling the function, which mean it still executed within the same process as the tests. All is well here but in reality, this is not how the task function will be executed. So the <code>_sync()</code> function above would simulate how the task would enter and popped out of the queue to be executed.</p>
<p>The side effect is that the current transaction also will be closed and remember again how django TestCase works? Long story shot our tests will break. So when writing tests that need to utilized the <code>sync</code> parameter you have to use <code>TransactionTestCase</code>.</p>
]]></content:encoded></item><item><title><![CDATA[Stuff I found today #1]]></title><description><![CDATA[Aleph.js
Web framework in Deno. What's special I think it geared towards frontend developer. One thing caught my eyes is the use of pages routing. So you drop a file in ./pages/about.tsx and it will available as /about/ url. Reminds me to PHP ;)
You ...]]></description><link>https://grep.koditi.my/stuff-i-found-today-1</link><guid isPermaLink="true">https://grep.koditi.my/stuff-i-found-today-1</guid><category><![CDATA[Deno]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Kamal Mustafa]]></dc:creator><pubDate>Sat, 28 Nov 2020 22:27:17 GMT</pubDate><content:encoded><![CDATA[<h2 id="alephjs">Aleph.js</h2>
<p>Web framework in Deno. What's special I think it geared towards frontend developer. One thing caught my eyes is the use of pages routing. So you drop a file in <code>./pages/about.tsx</code> and it will available as <code>/about/</code> url. Reminds me to PHP ;)</p>
<p>You can also drop few other file formats in <code>/pages/</code> such as markdown and that will be rendered by Aleph.</p>
<p>Aleph looks interesting and made me thinking of building something similar in Python. Imagine being able to drop file written in Jinja2 in <code>./pages/</code> and automatically get it rendered.</p>
<h2 id="marshmellow">marshmellow</h2>
<p>Thinking about python web framework inspired by Aleph about lead me to search for serializer library, something similar to Django Rest Framework (DRF) serializer but more framework agnostic.</p>
<p>https://marshmallow.readthedocs.io/en/stable/why.html</p>
<h2 id="twtxt">twtxt</h2>
<h2 id="gemini">gemini</h2>
<p>https://gemini.circumlunar.space/</p>
<h2 id="myst">myst</h2>
<p>https://myst-parser.readthedocs.io/en/latest/</p>
]]></content:encoded></item></channel></rss>