<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Ming Di Leom</title>
  <icon>https://mdleom.com/svg/favicon.svg</icon>
  <subtitle>Microblog</subtitle>
  <link href="https://mdleom.com/atom-microblog.xml" rel="self"/>
  
  <link href="https://mdleom.com/"/>
  <updated>2026-08-02T00:00:00.000Z</updated>
  <id>https://mdleom.com/</id>
  
  <author>
    <name>Ming Di Leom</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>GoodWe ESA ESS Network Requirements</title>
    <link href="https://mdleom.com/microblog/2026/07/31/goodwe-esa-ess-network-requirements/"/>
    <id>https://mdleom.com/microblog/2026/07/31/goodwe-esa-ess-network-requirements/</id>
    <published>2026-07-31T00:00:00.000Z</published>
    <updated>2026-08-02T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Disclaimer: Unofficial article, use at your own risk.</p></blockquote><p>DNS queries and IP+port connections are monitored through OpenWRT <a href="/blog/2026/07/30/dns-dhcp-logs-openwrt/">dnsmasq and conntrack</a> (NAT table).</p><p>SEMS+ iOS app:</p><ul><li>ali-stats.jpush.cn*</li><li>app-analytics-services.com*</li><li>app-measurement.com*</li><li>au-gateway.semsportal.com</li><li>au.mqtt.goodwe-power.com</li><li>ce3e75d5.jpush.cn*</li><li>clients3.google.com</li><li>config.jpush.cn*</li><li>firebaseinstallations.googleapis.com</li><li>firebaselogging-pa.googleapis.com</li><li>firebaseremoteconfig.googleapis.com</li><li>firebase-settings.crashlytics.com</li><li>gd-stats.jpush.cn*</li><li>hk-gateway.semsportal.com</li><li>o.alicdn.com</li><li>oversea-gops-hk.oss-cn-hongkong.aliyuncs.com</li><li>semsplus.goodwe.com</li><li>semsplus.oss-cn-hongkong.aliyuncs.com</li><li>sentry1.semsportal.com</li><li>sentry.io</li><li>sis.jpush.io*</li><li>status-ipv6.jpush.cn*</li><li>tsis.jpush.cn*</li></ul><p>*Optional domains, commonly blocked by adblocker.</p><p>SEMS+ Web portal:</p><ul><li>a1320.dscb.akamai.net</li><li>au-gateway.semsportal.com</li><li>au-semsplus.goodwe.com</li><li>cloudauth-device-dualstack.cn-shanghai.aliyuncs.com</li><li>g.alicdn.com</li><li>netty-wss-au.iot.goodwe-power.com:8885</li><li>o.alicdn.com</li><li>o.alicdn.com.w.cdngslb.com</li><li>popunify-large-dualstack-1.cn-shanghai.aliyuncs.com.vipgds.alibabadns.com</li><li>semsplus.oss-accelerate.aliyuncs.com</li><li>semsplus.oss-cn-hongkong.aliyuncs.com</li></ul><p>If browser setting has WebGL enabled but NoScript is configured to restrict it, configure NoScript to allow WebGL on au-semsplus.goodwe.com, otherwise the page freezes in less than a minute. The portal actually works fine without WebGL, but it has to be disabled in the browser setting, which disables for every websites.</p><p>GoodWe ESA connected to wifi using WiFi&#x2F;LAN Kit-20 dongle:</p><p>Hostname: GW_WIFILAN_2<br>MAC address: C0:CD:D6:xx:xx:xx (Espressif)</p><ul><li>broker.goodwe-power.com:8883</li><li>iot-cn.oss-cn-hangzhou.aliyuncs.com:443</li><li>iot.goodwe-power.com:80</li><li>jiankong-hk.oss-accelerate.aliyuncs.com:80</li><li>www.goodwe.com (icmp)</li></ul><p>There’s also DNS traffic to OpenDNS where router can intercept using <a href="/blog/2026/07/30/dns-dhcp-logs-openwrt/#create-a-new-network">port forwarding rule</a>.</p><table><thead><tr><th>IP</th><th>Protocol</th><th>Port</th></tr></thead><tbody><tr><td>208.67.222.222</td><td>udp</td><td>53</td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Disclaimer: Unofficial article, use at your own risk.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;DNS queries and IP+port connections are monitored</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Xiaomi Mijia Mi Robot Vacuum-Mop 2 (MJST1S) Network Requirements</title>
    <link href="https://mdleom.com/microblog/2026/07/30/xiaomi-mijia-mi-robot-vacuum-mop-2-mjst1s-network-requirements/"/>
    <id>https://mdleom.com/microblog/2026/07/30/xiaomi-mijia-mi-robot-vacuum-mop-2-mjst1s-network-requirements/</id>
    <published>2026-07-30T00:00:00.000Z</published>
    <updated>2026-08-09T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Disclaimer: Unofficial article, use at your own risk.</p></blockquote><p>DNS queries and IP+port connections are monitored through OpenWRT <a href="/blog/2026/07/30/dns-dhcp-logs-openwrt/">dnsmasq and conntrack</a> (NAT table).</p><p>Initial wifi SSID: ijai-vacuum-v2_miapxxxx<br>Hostname: rk3308_robot32<br>MAC address: 58:B6:23:xx:xx:xx (Beijing Xiaomi Mobile Software)<br>Xiaomi Home server: Chinese mainland</p><p>Mi Robot Vacuum-Mop 2:</p><ul><li>cnbj2.fds.api.xiaomi.com:443</li><li>dlg.io.mi.com</li><li>ot.io.mi.com:8053*</li><li>ots.io.mi.com:443</li></ul><p>Also these IPs without corresponding domains,</p><table><thead><tr><th>IP</th><th>Protocol</th><th>Port</th></tr></thead><tbody><tr><td>36.156.49.91</td><td>udp</td><td>80</td></tr><tr><td>110.43.0.83</td><td>tcp</td><td>443</td></tr><tr><td>120.92.158.163</td><td>tcp</td><td>443</td></tr><tr><td>124.251.34.212</td><td>tcp</td><td>443</td></tr><tr><td>39.101.90.191</td><td>udp</td><td>8053*</td></tr><tr><td>120.52.181.238</td><td>udp</td><td>8053*</td></tr><tr><td>120.52.181.239</td><td>udp</td><td>8053*</td></tr><tr><td>120.92.146.6</td><td>udp</td><td>8053*</td></tr><tr><td>120.92.65.237</td><td>udp</td><td>8053*</td></tr><tr><td>120.92.65.254</td><td>udp</td><td>8053*</td></tr><tr><td>121.228.168.18</td><td>udp</td><td>8053*</td></tr><tr><td>123.125.102.216</td><td>udp</td><td>8053*</td></tr><tr><td>124.251.101.16</td><td>udp</td><td>8053*</td></tr><tr><td>220.181.106.199</td><td>udp</td><td>8053*</td></tr><tr><td>220.181.106.200</td><td>udp</td><td>8053*</td></tr></tbody></table><p>*Optional port, I don’t notice any issue after blocking it.</p><p>Xiaomi Home Android app:</p><ul><li>account.xiaomi.com</li><li>api.account.xiaomi.com</li><li>api.device.xiaomi.net</li><li>api.mijia.tech</li><li>api.miwifi.com</li><li>apm-rum.inf.miui.com*</li><li>app.chat.global.xiaomi.net</li><li>cdn.cnbj0.fds.api.mi-img.com</li><li>cdn.cnbj1.fds.api.mi-img.com</li><li>cdn.web-global.fds.api.mi-img.com</li><li>chat.kefu.mi.com</li><li>cnbj1.fds.api.xiaomi.com</li><li>core.api.mijia.tech</li><li>data.sec.miui.com*</li><li>firebase.googleapis.com</li><li>firebaseinappmessaging.googleapis.com</li><li>firebaseinstallations.googleapis.com</li><li>firebaselogging.googleapis.com</li><li>font.sec.miui.com</li><li>graph.facebook.com</li><li>home.mi.com</li><li>i.ai.mi.com</li><li>i.huodong.mi.com</li><li>iot-cdn0.io.mi.com</li><li>iot-cdn.io.mi.com</li><li>mcfe–account-static-legacy.cnbj1.mi-fds.com</li><li>m.mi.com</li><li>register.xmpush.global.xiaomi.com</li><li>resolver.msg.global.xiaomi.net</li><li>sdkconfig.xiaomi.com</li><li>shopapi.io.mi.com</li><li>ssl-cdn.static.browser.mi-img.com</li><li>static-verify.sec.xiaomi.com</li><li>stream.api.mijia.tech</li><li>sts.api.mijia.tech</li><li>stun.services.mozilla.com</li><li>tracking.miui.com*</li><li>tsmapi.pay.xiaomi.com</li><li>verify.sec.xiaomi.com</li><li>www.google-analytics.com*</li><li>www.googletagmanager.com</li></ul><p>*Optional domains, commonly blocked by adblocker.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Disclaimer: Unofficial article, use at your own risk.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;DNS queries and IP+port connections are monitored</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Framework Laptop 13 USB not working (solved)</title>
    <link href="https://mdleom.com/microblog/2026/06/27/framework-laptop-13-usb-not-working-solved/"/>
    <id>https://mdleom.com/microblog/2026/06/27/framework-laptop-13-usb-not-working-solved/</id>
    <published>2026-06-27T00:00:00.000Z</published>
    <updated>2026-06-27T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>The far left USB-C expansion card of my Framework Laptop 13 with AMD AI 300 suddenly stopped working after wake from suspend. Keyboard stopped working and couldn’t charge through that port. I took out the expansion card and plugged in to the near left USB-C expansion card (so two expansion cards) then plugged in my keyboard, that worked just fine so that expansion card was still functional. Then, I tried charging through the far left port directly without a USB-C expansion card, but the charging indicator light didn’t light up.</p><p>I searched around the web and tried <a href="https://community.frame.work/t/help-the-return-of-the-half-my-usb-ports-stopped-working-randomly/77757/3">this suggestion</a>. I powered it off and unplugged charger, waited around 2 minutes and switched back on. Keyboard and charging then started to work on the far left port with expansion card again. I didn’t have to disconnect the battery</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;The far left USB-C expansion card of my Framework Laptop 13 with AMD AI 300 suddenly stopped working after wake from suspend. Keyboard st</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>*.cdn.cloudflare.net CNAME is not available for NS record</title>
    <link href="https://mdleom.com/microblog/2026/05/23/cdn-cloudflare-net-cname-is-not-available-for-ns-record/"/>
    <id>https://mdleom.com/microblog/2026/05/23/cdn-cloudflare-net-cname-is-not-available-for-ns-record/</id>
    <published>2026-05-23T00:00:00.000Z</published>
    <updated>2026-05-24T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Cloudflare offers a nifty shortcut for split DNS situation. Company may share the same domain name for their public and internal domain. In the internal domain, some subdomains can point to internal IP address that are not available on public DNS records. Due to separation of DNS nameserver, DNS admin often need to replicate any creation of public subdomains into internal nameserver including subsequent update of values.</p><p>Instead of replicating every public DNS records (A&#x2F;AAAA&#x2F;CNAME&#x2F;MX&#x2F;TXT&#x2F;etc) of a subdomain, simply create a CNAME record that point to <code>&lt;fqdn&gt;.cdn.cloudflare.net</code> in the internal nameserver. This works with subdomains that have proxy mode enabled and also those without.</p><p>Internal nameserver:</p><table><thead><tr><th>Domain</th><th>Type</th><th>Value</th></tr></thead><tbody><tr><td><code>a.example.com</code></td><td>CNAME</td><td><code>a.example.com.cdn.cloudflare.net</code></td></tr></tbody></table><p>However, this CNAME shortcut does not support subdomain that NS to third-party DNS provider, even with CNAME flattening enabled. In the following examples, <code>b.example.com.cdn.cloudflare.net</code> does not return any record.</p><p>Cloudflare DNS:</p><table><thead><tr><th>Domain</th><th>Type</th><th>Value</th></tr></thead><tbody><tr><td><code>b.example.com</code></td><td>NS</td><td><code>ns-xx.awsdns-xx.com</code></td></tr></tbody></table><table><thead><tr><th>Domain</th><th>Type</th><th>Value</th></tr></thead><tbody><tr><td><code>b.example.com</code></td><td>CNAME</td><td><code>b.z.example.com</code></td></tr><tr><td><code>z.example.com</code></td><td>NS</td><td><code>b1.example.com</code></td></tr><tr><td><code>b1.example.com</code></td><td>A</td><td><code>x.x.x.x</code></td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Cloudflare offers a nifty shortcut for split DNS situation. Company may share the same domain name for their public and internal domain. </summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Using Cloudflare Origin certificate on Salesforce</title>
    <link href="https://mdleom.com/microblog/2026/04/09/using-cloudflare-origin-certificate-on-salesforce/"/>
    <id>https://mdleom.com/microblog/2026/04/09/using-cloudflare-origin-certificate-on-salesforce/</id>
    <published>2026-04-09T00:00:00.000Z</published>
    <updated>2026-04-09T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>As certificate validity is now reducing to <a href="https://www.ibm.com/think/insights/new-era-for-certificate-management">200 days</a> in 2026, Salesforce administrators must find a practical way to manage certificate renewal. Ideally, Salesforce should support <a href="https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment">ACME protocol</a> to automate renewal.</p><p>For those utilising Cloudflare CDN, it is possible to install Cloudflare <a href="https://developers.cloudflare.com/ssl/origin-configuration/origin-ca/">Origin certificate</a> with 15-year validity on Salesforce. Although Salesforce <a href="https://help.salesforce.com/s/articleView?id=platform.domain_mgmt_cert_prereqs.htm&type=5">documentation</a> mentions the certificate must be CA-signed, self-signed certificate including Cloudflare Origin certificate (which is not <em>public</em> CA-signed) is actually acceptable.</p><ol><li>In Cloudflare, change the <a href="https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/full-strict/">encryption mode</a> to “Full (strict)”.</li><li>Enable <a href="https://developers.cloudflare.com/network/true-client-ip-header/">True-Client-IP</a> if on Enterprise plan.</li><li>Add CNAME record (if not exist) that points to the Salesforce site, e.g. <code>custom-domain.com.xxx.live.siteforce.com</code>. Ensure the CNAME record is in <a href="https://developers.cloudflare.com/dns/proxy-status/">Proxied&#x2F;orange-clouded</a> mode.</li><li>In Salesforce, generate and download certificate signing request (CSR).</li><li>In Cloudflare, create a new origin certificate, choose “Use my private key and CSR”, paste the CSR value, update hostname and Create it.</li><li>Copy the PEM value then append <a href="https://developers.cloudflare.com/ssl/static/origin_ca_rsa_root.pem">Origin CA</a> root certificate and save it with “.pem” extension. The file should have both leaf and root certificates.</li><li>Upload the “.pem” file to Salesforce.</li><li>Select the new certificate in Salesforce <a href="https://help.salesforce.com/s/articleView?id=platform.domain_mgmt_cert_setup.htm&type=5">domain configuration</a>.</li></ol><p>In the last step, <strong>do not</strong> select the “Use a third-party service or CDN” <a href="https://help.salesforce.com/s/articleView?id=platform.domain_mgmt_external_host_setup.htm&type=5">option</a> despite Cloudflare being a CDN service. That option will configure the Salesforce site to serve over HTTP, requiring “Flexible” encryption mode in Cloudflare which is not ideal.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;As certificate validity is now reducing to &lt;a href=&quot;https://www.ibm.com/think/insights/new-era-for-certificate-management&quot;&gt;200 days&lt;/a&gt; i</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Change M365 Bookings calendar sender address</title>
    <link href="https://mdleom.com/microblog/2026/03/15/change-m365-bookings-calendar-sender-address/"/>
    <id>https://mdleom.com/microblog/2026/03/15/change-m365-bookings-calendar-sender-address/</id>
    <published>2026-03-15T00:00:00.000Z</published>
    <updated>2026-03-15T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>TL;DR Update UPN and wait at least 24 hours.</p><p>During setup of M365 Bookings shared calendar, a company account is created with user principal name (UPN) in the format of <code>name@tenant.onmicrosoft.com</code>. This UPN is used as the sender address when sending out booking notifications. The issue is that <code>*.onmicrosoft.com</code> email domain is often abused for spams and many email providers (notably iCloud) are starting to reject any incoming email with that domain. Microsoft meanwhile has started to <a href="https://techcommunity.microsoft.com/blog/exchange/limiting-onmicrosoft-domain-usage-for-sending-emails/4446167">throttle</a> usage of that domain for sending email.</p><p>There are two ways to update the sender address:</p><ol><li><a href="https://learn.microsoft.com/en-us/microsoft-365/bookings/custom-domain-support">OWA mailbox policy</a></li><li><a href="https://blog.markdepalma.com/?p=681">Change UPN</a></li></ol><p>Method (1) is the Microsoft’s recommended method, but I prefer Method (2) due to visibility on Entra ID; anyone with read-only account within the tenant can view the updated UPN in Entra ID whereas only M365 admin can view mailbox policy. If a subdomain (e.g. <code>name@booking.example.com</code>) is preferred over the company’s email domain (<code>example.com</code>), the subdomain will need to be added to M365 with MX (<code>booking-example-com.mail.protection.outlook.com</code>), SPF (<code>&quot;v=spf1 include:spf.protection.outlook.com -all&quot;</code>) and DKIM DNS records, regardless of method (1) or (2).</p><p>If the booking calendar is embedded in the company website, the iframe link <code>https://outlook.office365.com/owa/calendar/name@tenant.onmicrosoft.com/bookings/</code> needs to be updated to <code>https://outlook.office365.com/owa/calendar/&lt;new-upn&gt;/bookings/</code>.</p><p>Once UPN is updated, it may take <em>at least a day</em> for the change to apply. In my experience, the new UPN is applied to new appointment’s notification within hours, but it only applied to cancellation notification after 24 hours (or longer).</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;TL;DR Update UPN and wait at least 24 hours.&lt;/p&gt;
&lt;p&gt;During setup of M365 Bookings shared calendar, a company account is created with user</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Opening a web browser from Java apps in WSL</title>
    <link href="https://mdleom.com/microblog/2026/03/06/opening-a-web-browser-from-java-apps-in-wsl/"/>
    <id>https://mdleom.com/microblog/2026/03/06/opening-a-web-browser-from-java-apps-in-wsl/</id>
    <published>2026-03-06T00:00:00.000Z</published>
    <updated>2026-03-06T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Linux CLI tools typically rely on xdg-open or fallback to $BROWSER environment variable to open the default browser, usually for OAuth authentication to authorise the CLI tool without using a permanent credential. Java apps behave differently by trying common browser executables in $PATH if the default browser could not be located. To launch a web browser from a Java app in WSL, simply add a symlink in “&#x2F;usr&#x2F;bin&#x2F;“ that points to the browser executable in Windows, this way you don’t have to install a <a href="https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps#install-google-chrome-for-linux">web browser</a> in WSL.</p><pre><code class="hljs plaintext"># Examples, just choose one.sudo ln -s &quot;/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe&quot; &quot;/usr/bin/msedge&quot;sudo ln -s &quot;/mnt/c/Program Files/Google/Chrome/Application/chrome.exe&quot; &quot;/usr/bin/chrome&quot;sudo ln -s &quot;/mnt/c/Program Files/Mozilla Firefox/firefox.exe&quot; /usr/bin/firefox&quot;</code></pre><p>Programs that rely on OAuth authentication typically listens on a random port to receive credential during the last step of authorisation. Since WSL enables IPv6 by default, the program may listen on IPv6 only. I noticed this behaviour when I check for listening ports <code>netstat -aon | findstr &quot;:port-number&quot;</code> on Windows and the output was <code>[::1]:port-number</code> without any <code>127.0.0.1</code>. This can cause the program to unable to receive credential and fail authorisation if the Windows host has IPv6 disabled. A workaround for this issue is to also disable IPv6 on WSL to force the program to listen on IPv4. Append this line to “$home\.wslconfig” in Windows and restart WSL <code>wsl --shutdown</code>,</p><pre><code class="hljs plaintext">[wsl2]kernelCommandLine=ipv6.disable=1</code></pre><p><a href="https://stackoverflow.com/questions/64159822/how-to-solve-java-lang-exception-no-web-browser-found-on-raspberry-pi#comment113470482_64160341">Credit</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Linux CLI tools typically rely on xdg-open or fallback to $BROWSER environment variable to open the default browser, usually for OAuth au</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Importing intermediate certificate into Chromium/Cromite</title>
    <link href="https://mdleom.com/microblog/2026/02/14/importing-intermediate-certificate-into-chromium-cromite/"/>
    <id>https://mdleom.com/microblog/2026/02/14/importing-intermediate-certificate-into-chromium-cromite/</id>
    <published>2026-02-14T00:00:00.000Z</published>
    <updated>2026-02-14T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Some websites <a href="https://incomplete-chain.badssl.com/">only serve</a> leaf&#x2F;server certificate instead of the usual certificate chain (leaf + intermediate). If a browser doesn’t have the corresponding intermediate certificate (that signs the leaf certificate) cached beforehand, this can cause certificate error.</p><p>To download the missing intermediate certificate, click on “Not secure” &gt; “Certificate details” &gt; Details tab &gt; “Authority Information Access”, there should be a link next to “CA Issuers”.</p><p>The easiest way is to import the downloaded certificate in Chromium&#x2F;<a href="https://github.com/uazo/cromite">Cromite</a> is to use the built-in Certificate Manager (<code>chrome://certificate-manager/localcerts/usercerts</code>). If you use <a href="https://wiki.archlinux.org/title/User:Grawity/Adding_a_trusted_CA_certificate#System-wide_%E2%80%93_Arch,_Fedora_(p11-kit)">p11-kit</a> (<code>trust anchor --store interCA.crt</code>) to import, Cromite may not necessarily trust it; in that case, in Certificate Manager (<code>chrome://certificate-manager/localcerts</code>), enable “Use imported local certificates…”.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Some websites &lt;a href=&quot;https://incomplete-chain.badssl.com/&quot;&gt;only serve&lt;/a&gt; leaf&amp;#x2F;server certificate instead of the usual certificate</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Extending LVM partition after disk expansion</title>
    <link href="https://mdleom.com/microblog/2026/01/17/extending-lvm-partition-after-disk-expansion/"/>
    <id>https://mdleom.com/microblog/2026/01/17/extending-lvm-partition-after-disk-expansion/</id>
    <published>2026-01-17T00:00:00.000Z</published>
    <updated>2026-02-23T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<ol><li>Boot GParted Live as this is best done offline.</li><li>GParted may prompt to fix the GPT header due to metadata mismatch about the disk size, select “Fix”.</li><li>Using GParted program, deactivate the LVM partition.</li><li>Resize the LVM partition by dragging the right-arrow to the end.</li><li>Click tick ✓ to apply. Resizing should take only a few seconds, if it’s not finished within a minute, reboot GParted Live and repeat; this may happen if Steps 2-3 are skipped.</li><li>Reactivate the LVM partition.</li><li>Launch Terminal,</li></ol><pre><code class="hljs plaintext">sudo -svgslvs</code></pre><ol start="8"><li><code>vgs</code> may show non-zero VFree value meaning the volume group contains unallocated space. <code>lvs</code> lists the volume group and logical volume, the values are used in <code>lvresize</code>; Ubuntu defaults to <code>ubuntu-vg/ubuntu-lv</code>, the slash is not an <em>OR</em>, both values with a slash are required.</li></ol><pre><code class="hljs plaintext">lvresize -l +100%FREE --resizefs VG-name/LV-namevgs</code></pre><ol start="9"><li><code>lvresize</code> may fail due to corrupted filesystem, skip this step if no error.</li></ol><pre><code class="hljs plaintext">e2fsck -f /dev/VG-name/LV-nameresize2fs /dev/VG-name/LV-name</code></pre><ol start="10"><li><code>vgs</code> should now show zero VFree value.</li><li>Reboot.</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;ol&gt;
&lt;li&gt;Boot GParted Live as this is best done offline.&lt;/li&gt;
&lt;li&gt;GParted may prompt to fix the GPT header due to metadata mismatch about th</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>GnuPG 2.5 for Windows is now 64-bit only</title>
    <link href="https://mdleom.com/microblog/2026/01/11/gnupg-2-5-for-windows-is-now-64-bit-only/"/>
    <id>https://mdleom.com/microblog/2026/01/11/gnupg-2-5-for-windows-is-now-64-bit-only/</id>
    <published>2026-01-11T00:00:00.000Z</published>
    <updated>2026-01-11T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>After updating GnuPG to 2.5.16 using Chocolatey, I wasn’t able to sign commit in WSL with pinentry error. The “$HOME&#x2F;.gnupg&#x2F;gpg-agent.conf” was previously configured with <code>pinentry-program &quot;/mnt/c/Program Files (x86)/gnupg/bin/pinentry-basic.exe&quot;</code> which is now an invalid path. I updated it to:</p><p><code>pinentry-program &quot;/mnt/c/Program Files/GnuPG/bin/pinentry-basic.exe&quot;</code></p><p>Then run <code>systemctl --user restart gpg-agent.service</code>.</p><p>If Git and GnuPG are used in Windows, the gpg config in “$HOME\.gitconfig” should be updated to:</p><pre><code class="hljs plaintext">[gpg]  program = C:\\Program Files\\GnuPG\\bin\\gpg.exe</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;After updating GnuPG to 2.5.16 using Chocolatey, I wasn’t able to sign commit in WSL with pinentry error. The “$HOME&amp;#x2F;.gnupg&amp;#x2F;gpg</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>GRUB 2.14rc1 supports LUKS2 + Argon2 disk encryption</title>
    <link href="https://mdleom.com/microblog/2026/01/07/grub-2-14rc1-supports-luks2-argon2-disk-encryption/"/>
    <id>https://mdleom.com/microblog/2026/01/07/grub-2-14rc1-supports-luks2-argon2-disk-encryption/</id>
    <published>2026-01-07T00:00:00.000Z</published>
    <updated>2026-01-07T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>I had always used <a href="https://aur.archlinux.org/packages/grub-improved-luks2-git">grub-improved-luks2-git</a> AUR package to boot up my <a href="https://wiki.archlinux.org/title/GRUB#LUKS2">LUKS2+Argon2-encrypted</a> disk. Now that GRUB 2.14rc1 supports it, it’s time to switch to the default package.</p><p><code>$ sudo pacman -S grub</code></p><p>pacman detected it conflicts with grub-improved-luks2-git and prompted for removal which is expected. Then, this is the most important part, “&#x2F;etc&#x2F;default&#x2F;grub” config has been restored to the default during installation, so I had to replace it with my config. Thankfully, pacman made a backup at “&#x2F;etc&#x2F;default&#x2F;grub.pacsave”, so I just need to move it back.</p><p><code>$ sudo mv /etc/default/grub.pacsave /etc/default/grub</code></p><p>Reinstall and regenerate the GRUB configuration.</p><pre><code class="hljs plaintext">sudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=&quot;Arch Linux&quot; --rechecksudo grub-mkconfig -o /boot/grub/grub.cfg</code></pre><p><code>booloader-id</code> value can be anything. The whole line of grub-mkconfig can be replaced with just <code>update-grub</code> (without any option) if the command is available.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;I had always used &lt;a href=&quot;https://aur.archlinux.org/packages/grub-improved-luks2-git&quot;&gt;grub-improved-luks2-git&lt;/a&gt; AUR package to boot up</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Importing FreeTube subscriptions to NewPipe/Tubular</title>
    <link href="https://mdleom.com/microblog/2025/10/10/importing-freetube-subscriptions-to-newpipe-tubular/"/>
    <id>https://mdleom.com/microblog/2025/10/10/importing-freetube-subscriptions-to-newpipe-tubular/</id>
    <published>2025-10-10T00:00:00.000Z</published>
    <updated>2025-10-10T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>In FreeTube, navigate to Settings &gt; Data &gt; Export Subscriptions &gt; Export YouTube (.csv).</p><p>Transfer the csv file to your mobile device.</p><p>In NewPipe&#x2F;Tubular, navigate to Subscriptions &gt; upper-left triple-dot &gt; Import from &gt; YouTube &gt; Import File &gt; choose the csv file.</p><p>The import will run in the background with notification. The app’s notification will clear once the import is complete.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;In FreeTube, navigate to Settings &amp;gt; Data &amp;gt; Export Subscriptions &amp;gt; Export YouTube (.csv).&lt;/p&gt;
&lt;p&gt;Transfer the csv file to your mo</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Using vector tiles on Nextcloud Maps</title>
    <link href="https://mdleom.com/microblog/2025/08/10/using-vector-tiles-on-nextcloud-maps/"/>
    <id>https://mdleom.com/microblog/2025/08/10/using-vector-tiles-on-nextcloud-maps/</id>
    <published>2025-08-10T00:00:00.000Z</published>
    <updated>2025-08-10T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Nextcloud Maps uses raster tiles by default, but it also supports <a href="https://wiki.openstreetmap.org/wiki/Vector_tiles">vector tiles</a> which looks nicer. Navigate to Nextcloud administration &gt; Additional settings (<code>&lt;nextcloud-domain&gt;/settings/admin/additional</code>) &gt; Maplibre settings. Set the style url as <code>https://tiles.openfreemap.org/styles/liberty</code>.</p><p>The style url can be set to other <a href="https://github.com/maplibre/awesome-maplibre#maptile-providers">map providers</a>. I use <a href="https://openfreemap.org/">OpenFreeMap</a> because it’s free and doesn’t need an API key. It’s available in <a href="https://openfreemap.org/quick_start/">4 styles</a>.</p><p>Maplibre uses WebGL to render the vector tiles, so if your browser block WebGL by default, your Nextcloud domain (not <code>openfreemap.org</code>) needs to be allowlisted for it.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Nextcloud Maps uses raster tiles by default, but it also supports &lt;a href=&quot;https://wiki.openstreetmap.org/wiki/Vector_tiles&quot;&gt;vector tiles</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Separate markdown headings into pages</title>
    <link href="https://mdleom.com/microblog/2025/07/27/separate-markdown-headings-into-pages/"/>
    <id>https://mdleom.com/microblog/2025/07/27/separate-markdown-headings-into-pages/</id>
    <published>2025-07-27T00:00:00.000Z</published>
    <updated>2025-07-27T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Previously the <a href="/threat-hunting/">Threat Hunting</a> page contained all search queries in one page separated by headings. That approach was untidy especially when conducting a web search; where after being redirected to the threat hunting page, still had to navigate to the relevant heading to locate the relevant search query.</p><p>Each heading or search query is now in a <a href="https://gitlab.com/curben/blog/-/commit/4922492c959627fe7beeb678c13e29efee79b540">separate page</a>, so once those new pages are indexed by search engine, the search result will lead directly to a page that only contains the relevant search query, e.g. <a href="/threat-hunting/filefix-detection">FileFix detection</a>.</p><pre><code class="hljs py"><span class="hljs-keyword">from</span> os <span class="hljs-keyword">import</span> chdir, path<span class="hljs-keyword">from</span> re <span class="hljs-keyword">import</span> S, findall, subchdir(path.dirname(__file__))template = <span class="hljs-string">&quot;&quot;&quot;---</span><span class="hljs-string">title: &#123;title&#125;</span><span class="hljs-string">layout: page</span><span class="hljs-string">date: 2025-07-27</span><span class="hljs-string">---</span><span class="hljs-string">&#123;content&#125;&quot;&quot;&quot;</span><span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(<span class="hljs-string">&quot;index.md&quot;</span>) <span class="hljs-keyword">as</span> f:    s = f.read()    <span class="hljs-comment"># https://stackoverflow.com/a/66619938</span>    <span class="hljs-keyword">for</span> title, content <span class="hljs-keyword">in</span> findall(<span class="hljs-string">r&quot;(?:^|\n)##\s([^\n]+)\n(.*?)(?=\n##?\s|$)&quot;</span>, s, S):        <span class="hljs-comment"># https://stackoverflow.com/a/74260791</span>        fname = sub(<span class="hljs-string">r&quot;\W+&quot;</span>, <span class="hljs-string">&quot;-&quot;</span>, title).strip(<span class="hljs-string">&quot;-&quot;</span>).lower()        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(fname + <span class="hljs-string">&quot;.md&quot;</span>, <span class="hljs-string">&quot;w&quot;</span>) <span class="hljs-keyword">as</span> w:            w.write(template.<span class="hljs-built_in">format</span>(title=title, content=content))        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(<span class="hljs-string">&quot;index-new.md&quot;</span>, <span class="hljs-string">&quot;a&quot;</span>) <span class="hljs-keyword">as</span> a:            a.write(<span class="hljs-string">f&quot;- [<span class="hljs-subst">&#123;title&#125;</span>](<span class="hljs-subst">&#123;fname&#125;</span>)\n&quot;</span>)</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Previously the &lt;a href=&quot;/threat-hunting/&quot;&gt;Threat Hunting&lt;/a&gt; page contained all search queries in one page separated by headings. That ap</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>linux-firmware meta package on Arch Linux</title>
    <link href="https://mdleom.com/microblog/2025/07/18/linux-firmware-meta-package-on-arch-linux/"/>
    <id>https://mdleom.com/microblog/2025/07/18/linux-firmware-meta-package-on-arch-linux/</id>
    <published>2025-07-18T00:00:00.000Z</published>
    <updated>2025-07-18T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Arch Linux <a href="https://archlinux.org/packages/core/any/linux-firmware/">linux-firmware</a> is now a meta package. Its derivative Manjaro renamed it to linux-firmware-meta. The default set covers a wide range of firmwares that may not be applicable to most devices and can be trimmed down.</p><ol><li>Remove the meta package, <code>pacman -Rn linux-firmware</code></li><li>Identify device manufacturer, <code>lspci</code></li><li>Remove irrelevant firmware, e.g. if Nvidia device is not installed, <code>pacman -Rns linux-firmware-nvidia</code></li><li>Mark the necessary firmware as explicitly installed, <code>pacman -D --asexplicit $(pacman -Qs -q linux-firmware | sed -z &#39;s|\n| |g&#39;)</code><ul><li>This is necessary to avoid removing them when <a href="https://wiki.archlinux.org/title/Pacman/Tips_and_tricks#Removing_unused_packages_(orphans)">removing orphans</a>.</li></ul></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Arch Linux &lt;a href=&quot;https://archlinux.org/packages/core/any/linux-firmware/&quot;&gt;linux-firmware&lt;/a&gt; is now a meta package. Its derivative Man</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Defender for Endpoint on Alma</title>
    <link href="https://mdleom.com/microblog/2025/07/05/defender-for-endpoint-on-alma/"/>
    <id>https://mdleom.com/microblog/2025/07/05/defender-for-endpoint-on-alma/</id>
    <published>2025-07-05T00:00:00.000Z</published>
    <updated>2025-07-05T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Microsoft Defender for Endpoint now <a href="https://learn.microsoft.com/en-us/defender-endpoint/mde-linux-prerequisites#supported-linux-distributions">supports Alma</a>, probably requires <code>mdatp</code> at least v101.25042.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Microsoft Defender for Endpoint now &lt;a href=&quot;https://learn.microsoft.com/en-us/defender-endpoint/mde-linux-prerequisites#supported-linux-</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>CMC 3.37.0 switched to _cmc_summary index</title>
    <link href="https://mdleom.com/microblog/2025/04/03/cmc-3-37-0-switched-to-cmc-summary-index/"/>
    <id>https://mdleom.com/microblog/2025/04/03/cmc-3-37-0-switched-to-cmc-summary-index/</id>
    <published>2025-04-03T00:00:00.000Z</published>
    <updated>2025-04-04T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Splunk Cloud Monitoring Console (CMC) app which auto-update itself separately from Splunk Cloud recently switched from “summary” to “_cmc_summary” index in <a href="https://docs.splunk.com/Documentation/SplunkCloud/9.3.2411/ReleaseNotes/CloudMonitoringConsole">3.37.0 update</a> released on 27 March 2025. The update broke my custom license monitoring dashboards and alert. Resolved the issue by including the new index.</p><p>Update: CMC 3.37.1 update reverts it back to “summary” index to allow for <a href="https://splunk.my.site.com/customer/s/article/CMC-new-summary-index">gradual transition</a>.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Splunk Cloud Monitoring Console (CMC) app which auto-update itself separately from Splunk Cloud recently switched from “summary” to “_cmc</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Opera&#39;s built-in adblocker does not support strict blocking</title>
    <link href="https://mdleom.com/microblog/2025/02/16/opera-s-built-in-adblocker-does-not-support-strict-blocking/"/>
    <id>https://mdleom.com/microblog/2025/02/16/opera-s-built-in-adblocker-does-not-support-strict-blocking/</id>
    <published>2025-02-16T00:00:00.000Z</published>
    <updated>2025-02-16T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Unlike <a href="https://github.com/gorhill/ublock/wiki/Strict-blocking">uBlock Origin</a>, when using <code>||example.com^</code> filter in the Opera’s built-in adblocker, it does not block the webpage itself, instead only the secondary resources (e.g. <code>example.com/script.js</code>). This limitation also notably presents in <a href="https://forum.adblockplus.org/viewtopic.php?t=18774#p85439">Adblock Plus</a>.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Unlike &lt;a href=&quot;https://github.com/gorhill/ublock/wiki/Strict-blocking&quot;&gt;uBlock Origin&lt;/a&gt;, when using &lt;code&gt;||example.com^&lt;/code&gt; filter </summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Unable to get SSH host key due to short timeout</title>
    <link href="https://mdleom.com/microblog/2025/02/08/unable-to-get-ssh-host-key-due-to-short-timeout/"/>
    <id>https://mdleom.com/microblog/2025/02/08/unable-to-get-ssh-host-key-due-to-short-timeout/</id>
    <published>2025-02-08T00:00:00.000Z</published>
    <updated>2025-02-08T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Some servers can be a bit slow in responding to host key request, so <code>ssh-keyscan</code> may return empty result when using the default 5 seconds timeout. A workaround is to <a href="https://unix.stackexchange.com/a/443251">increase the timeout</a>, <code>ssh-keyscan -T 10 hostname</code> for 10 seconds.</p><p>Noticed this issue from host key verification error when mirroring a GitLab repository to an external repository. I could not fix it because I can’t modify the SSH client configuration in GitLab.com to increase <code>ConnectTimeout</code>.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Some servers can be a bit slow in responding to host key request, so &lt;code&gt;ssh-keyscan&lt;/code&gt; may return empty result when using the defa</summary>
      
    
    
    
  </entry>
  
  <entry>
    <title>Do not disable Android lockscreen</title>
    <link href="https://mdleom.com/microblog/2025/02/01/do-not-disable-android-lockscreen/"/>
    <id>https://mdleom.com/microblog/2025/02/01/do-not-disable-android-lockscreen/</id>
    <published>2025-02-01T00:00:00.000Z</published>
    <updated>2025-02-01T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Disabling the Android lockscreen may cause the display to turn on by itself, either after timeout or power button. Worse, after turning on, it may stay on and does not timeout. I experienced this issue in LineageOS 21 and 22. Apparently, this issue has been around since <a href="https://www.reddit.com/r/LineageOS/comments/k2p71n/screen_keeps_turning_on/">17</a> and another one reported with version <a href="https://gitlab.com/LineageOS/issues/android/-/issues/5970">20</a>.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Disabling the Android lockscreen may cause the display to turn on by itself, either after timeout or power button. Worse, after turning o</summary>
      
    
    
    
  </entry>
  
</feed>
