<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>DevOps on Margrop Blog</title>
		<link>https://blog.margrop.net/en/tag/devops/</link>
		<description>Recent content in DevOps on Margrop Blog</description>
		<generator>Hugo</generator>
		<language>en-US</language>
		
		
		
		
			<lastBuildDate>Sat, 13 Jun 2026 08:00:00 +0800</lastBuildDate>
		
			<atom:link href="https://blog.margrop.net/en/tag/devops/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>The Portainer 500 Error That Wasn&#39;t the YAML — A Two-librespeed_default-Network Story</title>
				<link>https://blog.margrop.net/en/post/portainer-500-duplicate-compose-network-enigma/</link>
				<pubDate>Sat, 13 Jun 2026 08:00:00 +0800</pubDate>
				<guid>https://blog.margrop.net/en/post/portainer-500-duplicate-compose-network-enigma/</guid>
				<description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;You update a stack in Portainer. The browser slaps you with a red &lt;code&gt;500 Internal Server Error&lt;/code&gt;. You assume the YAML is wrong, fix the indentation, swap the quotes, drop the image tag. You hit Update again. Same 500. And again. And again.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The real culprit is buried at the deepest level of the HTTP response body: &lt;code&gt;network librespeed_default is ambiguous (2 matches found on name)&lt;/code&gt;.&lt;/strong&gt; Two networks with the same name exist in the same Docker engine—two IDs, two &lt;code&gt;Created&lt;/code&gt; timestamps, two &lt;code&gt;com.docker.compose.config-hash&lt;/code&gt; labels, but a single shared name. Compose asks the engine to look up that name; the engine refuses to pick between them; &lt;code&gt;compose up&lt;/code&gt; fails; Portainer wraps the error as a 500 and returns it to your browser.&lt;/p&gt;&#xA;&lt;p&gt;The fix is embarrassingly simple: &lt;strong&gt;delete one of the two orphan networks&lt;/strong&gt; (&lt;code&gt;docker network rm &amp;lt;id&amp;gt;&lt;/code&gt; or click Remove in Portainer&amp;rsquo;s Networks page), then re-run Update the stack with the exact same content. It just works.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This post is a real debugging session: a stack update that was supposed to be a 30-second mount-path change turned into a 1-hour investigation. All private details (internal addresses, registry URLs, volume paths, container names, credentials) have been replaced with &lt;code&gt;&amp;lt;PLACEHOLDER&amp;gt;&lt;/code&gt;. Only public source code, official docs, and the error text itself are preserved.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Your Background Process Keeps Dying After Shell Exit? Stop Blaming nohup — Understand setsid and Unix Process Lifecycle</title>
				<link>https://blog.margrop.net/en/post/setsid-daemon-process-survival/</link>
				<pubDate>Mon, 01 Jun 2026 07:00:00 +0800</pubDate>
				<guid>https://blog.margrop.net/en/post/setsid-daemon-process-survival/</guid>
				<description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;You launched a background service. You used &lt;code&gt;nohup&lt;/code&gt;. You added &lt;code&gt;&amp;amp;&lt;/code&gt;. You redirected output. Everything looked fine. But when you closed the terminal, disconnected SSH, or—more insidiously—when an automation tool finished executing its script, the process silently vanished. You checked &lt;code&gt;ps&lt;/code&gt;—nothing. You checked the port—nothing. No error in the logs. If this has happened to you, you&amp;rsquo;re not alone.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The root cause isn&amp;rsquo;t SIGHUP—at least, not entirely. The real issue is process group and session membership.&lt;/strong&gt; &lt;code&gt;nohup&lt;/code&gt; merely tells the process to ignore the SIGHUP signal, but the process still belongs to the same session and process group as the parent shell. When the shell exits and sends SIGHUP to the entire process group, &lt;code&gt;nohup&lt;/code&gt; helps—but only if the signal delivery chain stops there. If the controlling terminal is destroyed, if stdin/stdout pipes break, or if the parent&amp;rsquo;s process group is collectively reaped, the process can still die. The proper fix is &lt;code&gt;setsid&lt;/code&gt;: it creates a brand-new session, detaches from the controlling terminal, and places the process in its own process group—so SIGHUP from the parent shell never reaches it in the first place.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This post is based on a real debugging session of the CLIProxyAPI service. The service was started with &lt;code&gt;nohup&lt;/code&gt; but kept disappearing every time an automation script finished. The management panel kept showing &amp;ldquo;connection failed.&amp;rdquo; The culprit turned out to be a subtle Unix process lifecycle issue. This class of problem is common in cloud dev machines, CI runners, SSH jump hosts, and containerized workflows, but it&amp;rsquo;s often misdiagnosed because the underlying process model is poorly understood.&lt;/p&gt;&#xA;&lt;p&gt;All private details have been removed. No real internal addresses, tokens, or paths appear in this article.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Python App in macOS LaunchAgent Can&#39;t Reach the Internet? Here&#39;s the httpx Proxy Trap You Need to Know</title>
				<link>https://blog.margrop.net/en/post/macos-launchagent-python-httpx-proxy-no-route-to-host/</link>
				<pubDate>Sat, 30 May 2026 09:00:00 +0800</pubDate>
				<guid>https://blog.margrop.net/en/post/macos-launchagent-python-httpx-proxy-no-route-to-host/</guid>
				<description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;When a Python application using httpx with &lt;code&gt;trust_env=True&lt;/code&gt; runs inside a macOS LaunchAgent, it silently picks up the system proxy settings from &lt;code&gt;scutil --proxy&lt;/code&gt;. But the LaunchAgent process may not be able to reach that proxy server at all — resulting in &lt;code&gt;All connection attempts failed&lt;/code&gt; or &lt;code&gt;No route to host&lt;/code&gt; errors.&lt;/p&gt;&#xA;&lt;p&gt;The fix is one line: add &lt;code&gt;NO_PROXY=*&lt;/code&gt; to the LaunchAgent&amp;rsquo;s plist &lt;code&gt;EnvironmentVariables&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This article documents the full debugging journey: from discovering that my AI Agent&amp;rsquo;s WeChat Enterprise (WeCom) messages weren&amp;rsquo;t being replied to, through methodical proxy troubleshooting, to finally pinning down the root cause — macOS system proxy + LaunchAgent network isolation. We&amp;rsquo;ll dive deep into Python httpx source code, macOS proxy architecture, and the many gotchas of LaunchAgent runtime environments.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
			</item>
			<item>
				<title>The Complete AI Agent Migration Guide: A Real-World Journey from Scratch with 9 Pitfalls and Fixes</title>
				<link>https://blog.margrop.net/en/post/ai-agent-complete-migration-guide-from-scratch/</link>
				<pubDate>Sat, 30 May 2026 08:00:00 +0800</pubDate>
				<guid>https://blog.margrop.net/en/post/ai-agent-complete-migration-guide-from-scratch/</guid>
				<description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Migrating from one AI Agent platform to another sounds like a &amp;ldquo;copy and paste&amp;rdquo; job. In reality, it&amp;rsquo;s a systems engineering challenge involving data migration, channel integration, scheduled tasks, auto-start configuration, proxy settings, and module compatibility.&lt;/p&gt;&#xA;&lt;p&gt;This article documents my complete migration journey: from installing the new platform, migrating memories and skills, configuring messaging channels, debugging mysterious failures, to achieving fully automated operation. 9 pitfalls encountered, 9 solutions found — all shared here.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
			</item>
			<item>
				<title>Migrating from OpenClaw to HermesAgent: A Smooth AI Agent Migration in Practice</title>
				<link>https://blog.margrop.net/en/post/openclaw-to-hermesagent-migration/</link>
				<pubDate>Fri, 29 May 2026 08:00:00 +0800</pubDate>
				<guid>https://blog.margrop.net/en/post/openclaw-to-hermesagent-migration/</guid>
				<description>&lt;h2 id=&#34;introduction-why-am-i-moving&#34;&gt;Introduction: Why Am I &amp;ldquo;Moving&amp;rdquo;?&lt;/h2&gt;&#xA;&lt;p&gt;In 2026, the AI agent landscape is evolving at an incredible pace. Tools that were cutting-edge six months ago might already be surpassed by more powerful alternatives. As someone who relies heavily on AI agents for daily work, I&amp;rsquo;ve been closely following the latest developments in this field.&lt;/p&gt;&#xA;&lt;p&gt;Recently, I completed a full migration from OpenClaw to HermesAgent. This wasn&amp;rsquo;t an impulsive decision, but a thoughtful choice made after experiencing the differences between the two tools in real-world usage.&lt;/p&gt;&#xA;&lt;p&gt;This article will share in detail:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Background&lt;/strong&gt;: What OpenClaw and HermesAgent are&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Migration Reasons&lt;/strong&gt;: Why I decided to migrate&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Migration Process&lt;/strong&gt;: Step-by-step guide to complete the migration&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pitfalls Encountered&lt;/strong&gt;: Problems and solutions during migration&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Post-Migration Cleanup&lt;/strong&gt;: How to completely remove OpenClaw residuals&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Post-Migration Experience&lt;/strong&gt;: New capabilities gained from HermesAgent&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Q&amp;amp;A&lt;/strong&gt;: Frequently asked questions&lt;/li&gt;&#xA;&lt;/ol&gt;</description>
			</item>
	</channel>
</rss>
