[{"categories":null,"content":"# zeroclaw-profile.yaml config: cloud-init.user-data: | #cloud-config ssh_genkeytypes: [] ssh_pwauth: false package_update: true packages: - curl - ca-certificates - tzdata users: - name: zeroclaw shell: /bin/sh home: /home/zeroclaw system: true setup_home: true write_files: - path: /etc/init.d/zeroclaw permissions: \u0026#39;0755\u0026#39; owner: root:root content: | #!/sbin/openrc-run name=\u0026#34;zeroclaw\u0026#34; description=\u0026#34;ZeroClaw daemon\u0026#34; supervisor=supervise-daemon command=\u0026#34;/usr/local/bin/zeroclaw\u0026#34; command_args=\u0026#34;daemon\u0026#34; command_user=\u0026#34;zeroclaw:zeroclaw\u0026#34; directory=\u0026#34;/home/zeroclaw\u0026#34; depend() { need net after firewall } start_pre() { checkpath -d -m 0750 -o zeroclaw:zeroclaw /home/zeroclaw/.zeroclaw } runcmd: - ln -sf /usr/share/zoneinfo/Europe/Helsinki /etc/localtime - | curl -sSL -o /tmp/zeroclaw.tar.gz \\ https://github.com/zeroclaw-labs/zeroclaw/releases/latest/download/zeroclaw-x86_64-unknown-linux-musl.tar.gz tar -xzf /tmp/zeroclaw.tar.gz -C /usr/local/bin chmod 0755 /usr/local/bin/zeroclaw rm /tmp/zeroclaw.tar.gz limits.cpu: \u0026#34;2\u0026#34; limits.memory: 1GB # runtime is a few MB; 1GB is generous headroom # security.nesting not needed — Landlock sandbox requires no userns description: ZeroClaw trial on Alpine Edge devices: root: path: / pool: default type: disk size: 4GB eth0: name: eth0 nictype: bridged parent: br77 type: nic # incus profile edit zeroclaw \u0026lt; zeroclaw-profile.yaml # incus launch images:alpine/edge/cloud zeroclaw --profile zeroclaw config tweaks # incus exec zeroclaw -- su - zeroclaw -s /bin/sh -c \u0026#39;zeroclaw doctor\u0026#39; # incus exec zeroclaw -- vi /home/zeroclaw/.zeroclaw/config.toml # incus exec zeroclaw -- rc-service zeroclaw restart ","permalink":"https://mikael.srht.site/2026-07/zeroclaw-incus/","tags":null,"title":"ZeroClaw in an Incus LXC Container"},{"categories":null,"content":"A small update to the previous dynamic vlan setup . The goal is the same: a single ssid where each device gets dropped into the vlan assigned by the radius server, based on its mac address.\nThe change follows switching to a new device (cudy ap3000), with a more recent version of openwrt. The change moves to a single vlan99 interface for the AP\u0026rsquo;s own management traffic, while other vlan handling is dealt by the dynamic vlan setup.\nPackages Only needed to install the full wpad package (using apk on OpenWrt 24.x):\napk add wpad Wireless configuration config wifi-device \u0026#39;radio0\u0026#39; option type \u0026#39;mac80211\u0026#39; option path \u0026#39;platform/soc/18000000.wifi\u0026#39; option channel \u0026#39;1\u0026#39; option band \u0026#39;2g\u0026#39; option htmode \u0026#39;HE20\u0026#39; option disabled \u0026#39;0\u0026#39; option cell_density \u0026#39;0\u0026#39; option country \u0026#39;FI\u0026#39; config wifi-iface \u0026#39;default_radio0\u0026#39; option device \u0026#39;radio0\u0026#39; option mode \u0026#39;ap\u0026#39; option ssid \u0026#39;myssid\u0026#39; option encryption \u0026#39;psk2\u0026#39; option key \u0026#39;xxxxxxxxxxxx\u0026#39; option hostapd_options \u0026#39;macaddr_acl=2\u0026#39; option auth_cache \u0026#39;1\u0026#39; option dynamic_vlan \u0026#39;2\u0026#39; option vlan_bridge \u0026#39;br-lan\u0026#39; option vlan_naming \u0026#39;0\u0026#39; option vlan_tagged_interface \u0026#39;eth0\u0026#39; option wps_pushbutton \u0026#39;1\u0026#39; option wps_device_name \u0026#39;OpenWRT AP1\u0026#39; option auth_server \u0026#39;192.168.99.1\u0026#39; option auth_secret \u0026#39;xxxxxxxxx\u0026#39; option auth_port \u0026#39;1812\u0026#39; The radius server still assigns vlan ids per mac address. dynamic_vlan '2' enables tagged vlans, and vlan_naming '0' keeps the bridge device names predictable.\nBridge and interface br-lan has vlan filtering enabled vlan 99 is configured as a tagged local vlan on eth0 the default lan interface was replaced by vlan99 using dhcp client as protocol Minor optimization Disabled the firewall service in luci, since this device is only an AP and doesn\u0026rsquo;t need to route or filter traffic.\nReferences OpenWrt 802.1x wireless security OpenWrt basic wifi configuration ","permalink":"https://mikael.srht.site/2026-07/openwrt-dynamic-vlans-update/","tags":null,"title":"OpenWrt AP with dynamic vlans (update)"},{"categories":null,"content":"# 1. Fresh empty homeassistant folder (previous folder backed up and renamed) mkdir homeassistant \u0026amp;\u0026amp; cd homeassistant # 2. Install Python 3.13 uv python install 3.13.2 # 3. Create venv *in current directory* (no .venv/ subfolder) uv venv --python 3.13.2 --clear --seed . # 4. Activate (now bin/ activate are in ./ not .venv/) source bin/activate # 5. Install HA uv pip install homeassistant ","permalink":"https://mikael.srht.site/2026-04/homeassistant-core-uv/","tags":null,"title":"2026 04 02_HomeAssistant core with uv"},{"categories":null,"content":"Notes from trying out incus Context Looking into LXC containers. Considering a move from my monolithic debian server.\nHOST MACHINE Alpine linux I wanted low footprint and satisfy my own curiosity.\nVlans Essence of the setup :\n# /etc/network/interfaces ## host access on that vlan auto eth0.98 iface eth0.98 inet dhcp ## vlan 77 for containers, layer 2 only auto eth0.77 iface eth0.77 inet manual auto br77 iface br77 inet manual bridge_ports eth0.77 bridge_stp off ## no spanning tree, assume no loop bridge_fd 0 ## immediate forwarding, no delay for stp Incus initial setup # incus admin init single node setup locale storage pool default nat bridge created (incusbr0) BUT not to be used incus api not exposed over LAN Container setup Example mount:\nincus config device add dev repos disk \\ source=/srv/dev/repos \\ path=/home/user/repos dev box Some commands that I ran, some might have been useful, some not\nip addr show eth0 ip link set eth0 up udhcpc -i eth0 ping 192.168.1.2 cat /etc/network/interfaces apk add doas addgroup -g 1000 dev adduser -u 1000 -G dev -D dev echo \u0026#34;permit persist :dev as root\u0026#34; \u0026gt; /etc/doas.d/doas.conf apk add openssh rc-update add sshd default service sshd start Homeassistant A bit more \u0026ldquo;infra as code\u0026rdquo;:\nad hoc incus profile cloud-init for initial provisioning cat \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; \u0026gt; ha-profile.yaml config: cloud-init.user-data: | #cloud-config ssh_genkeytypes: [] ssh_pwauth: false package_update: true packages: - python3 - py3-pip - python3-dev - build-base - git - curl - jq - ffmpeg - openssl - ca-certificates - tzdata - libffi-dev users: - name: homeassistant groups: [audio, dialout, video] shell: /bin/sh home: /home/homeassistant system: true setup_home: true write_files: - path: /tmp/hass-install.sh permissions: \u0026#39;0755\u0026#39; owner: root:root content: | #!/bin/sh set -e # Ensure home dir exists just in case mkdir -p /home/homeassistant chown homeassistant:homeassistant /home/homeassistant cd /home/homeassistant # Setup VENV python3 -m venv . ./bin/pip install --upgrade pip wheel ./bin/pip install homeassistant aiohasupervisor mkdir -p .homeassistant chown -R homeassistant:homeassistant /home/homeassistant - path: /etc/init.d/homeassistant permissions: \u0026#39;0755\u0026#39; owner: root:root content: | #!/sbin/openrc-run name=\u0026#34;homeassistant\u0026#34; description=\u0026#34;Home Assistant\u0026#34; supervisor=supervise-daemon command=\u0026#34;/home/homeassistant/bin/hass\u0026#34; command_args=\u0026#34;-c /home/homeassistant/.homeassistant --log-file /var/log/homeassistant.log\u0026#34; command_user=\u0026#34;homeassistant:homeassistant\u0026#34; directory=\u0026#34;/home/homeassistant\u0026#34; depend() { need net after firewall } start_pre() { checkpath -d -m 0755 -o homeassistant:homeassistant /home/homeassistant/.homeassistant checkpath -f -m 0644 -o homeassistant:homeassistant /var/log/homeassistant.log } runcmd: - ln -sf /usr/share/zoneinfo/Europe/Helsinki /etc/localtime # Wait a beat for user creation to settle, then run from /tmp - /tmp/hass-install.sh - rc-update add homeassistant default - rc-service homeassistant start limits.cpu: \u0026#34;2\u0026#34; limits.memory: 2GB security.nesting: \u0026#34;true\u0026#34; description: Home Assistant Core on Alpine Edge devices: root: path: / pool: default type: disk size: 20GB eth0: name: eth0 nictype: bridged parent: br101 type: nic EOF # Then recreate incus stop homeassistant --force 2\u0026gt;/dev/null || true incus delete homeassistant incus profile edit homeassistant \u0026lt; ha-profile.yaml incus launch images:alpine/edge/cloud homeassistant --profile homeassistant Note that specific image is used to have cloud-init already installed. See linuxcontainers.org doc .\nWarning : this profile was generated by llm, better double check.\n","permalink":"https://mikael.srht.site/2026-04/incus-notes/","tags":null,"title":"2026 04 03 Incus notes"},{"categories":null,"content":"Overview Replaced OpenLDAP with GLauth, because I was looking for minimalistic setup. I noticed a bit too late that lldap might have been a better match\u0026hellip;\nFiles Created /usr/local/bin/glauth # Binary /etc/glauth/glauth.cfg # Configuration /etc/systemd/system/glauth.service # Systemd service /var/log/glauth/ # Logs (optional) Configuration Highlights Domain: dc=121013,dc=dpdns,dc=org LDAPS Port: 636 Certificates: /etc/letsencrypt/live/mydomain/ cert: fullchain.pem key: privkey.pem Users: admin, mikael, etc , observer Groups: parents (5001), kids (5002), services (5003) DN Format GLauth uses primary group as OU:\ncn=\u0026lt;username\u0026gt;,ou=\u0026lt;primarygroup\u0026gt;,dc=mydomain Examples:\ncn=admin,ou=parents,dc=mydomain cn=observer,ou=services,dc=mydomain Essential Commands Service Management sudo systemctl status glauth sudo systemctl restart glauth sudo journalctl -u glauth -f Testing Authentication # Basic auth test LDAPTLS_REQCERT=never ldapsearch -LLL -H ldaps://localhost:636 \\ -D cn=admin,ou=parents,dc=mydomain \\ -w password \\ -x \\ -b dc=mydomain \\ cn=mikael # Test TLS connection openssl s_client -connect localhost:636 -showcerts Generate Bcrypt Passwords htpasswd -bnBC 10 \u0026#34;\u0026#34; password | tr -d \u0026#39;:\u0026#39; | xxd -p Backup These Files /etc/glauth/glauth.cfg /etc/systemd/system/glauth.service Certificate renewal is automatic via Let\u0026rsquo;s Encrypt, but glauth needs restart after renewal (hot-reload doesn\u0026rsquo;t work for LDAPS section).\nObserver Account Created for service integrations (e.g., Tinyauth):\nUser: observer DN: cn=observer,ou=services,dc=mydomain Capability: Search-only access to entire directory Usage: LDAP_BIND_DN and LDAP_BIND_PASSWORD in service configs ","permalink":"https://mikael.srht.site/2025-12/glauth-ldap-server/","tags":null,"title":"quick ldap setup with glauth"},{"categories":null,"content":"Overview nasrepo automatically downloads .deb packages from builds.sr.ht and uses local-apt-repository to make them available via APT.\nArchitecture Timer (daily 3 AM) ↓ pull-artifacts ↓ /srv/local-apt-repository/ (.deb files) ↓ (systemd path monitoring) local-apt-repository (metadata generation) ↓ /var/lib/local-apt-repository/ (APT repository) ↓ apt install Components nasrepo package:\n/usr/lib/nasrepo/pull-artifacts - Downloads from builds.sr.ht /etc/nasrepo/.srht-token - OAuth token nasrepo-update.timer - Daily automation (3 AM) nasrepo-update.service - Runs pull-artifacts local-apt-repository package:\nMonitors /srv/local-apt-repository/ for new .deb files Generates metadata in /var/lib/local-apt-repository/ Configures APT source automatically Key Locations Token: /etc/nasrepo/.srht-token Downloads: /srv/local-apt-repository/ (input) Repository: /var/lib/local-apt-repository/ (output) APT source: /etc/apt/sources.list.d/local-apt-repository.list Manual Operations Download latest packages:\nsudo /usr/lib/nasrepo/pull-artifacts nascoll /srv/local-apt-repository Check timer status:\nsystemctl status nasrepo-update.timer Enable automatic updates:\nsudo systemctl enable --now nasrepo-update.timer Flow Timer triggers daily (or manual run) pull-artifacts queries builds.sr.ht for latest successful build Downloads all-packages.tar.gz, extracts .debs to /srv/local-apt-repository/ local-apt-repository.path detects new files local-apt-repository.service rebuilds metadata Packages immediately available via apt install ","permalink":"https://mikael.srht.site/2025-12/local-deb-repo/","tags":null,"title":"Local debian repo"},{"categories":null,"content":"Building Production Shelly Scripts with Modern JavaScript Introduction Shelly devices are one of my favorites IoT devices thanks to their built-in JavaScript support , but unless I\u0026rsquo;ve missed something, the developer experience is pretty poor :\nWrite ES5 syntax (no const, let, arrow functions) No modules or imports Trial-and-error testing on the device No type safety Manual minification if you hit the 25KB limit I played a bit with rollup to find out if I could improve a bit, and while my setup feels still like wip, it\u0026rsquo;s already alleviating the pain significantly enough (using es6+, type safety, tests) that I thought this could be nice to document (note I used an llm to produce this, so it\u0026rsquo;s way more verbose than what I\u0026rsquo;d produce normally)\nWhat we\u0026rsquo;re working with Shelly Gen2 devices run a modified Espruino interpreter with hard limits:\n25KB total script size (shared between code and runtime memory) 5 timers maximum ES5 syntax only (targets ~2009 JavaScript) No npm packages (can\u0026rsquo;t use most libraries) So yeah, the traditional approach is to write ES5 directly, paste it in the web UI, test on device, repeat. That works for small scripts, but it gets painful fast.\nHow the build pipeline works Here\u0026rsquo;s what happens when I run npm run build:\nModern JavaScript (src/) ↓ Type Check (JSDoc + TypeScript compiler) ↓ Lint (jshint) ↓ Test (Node.js built-in test runner) ↓ Bundle (Rollup - resolve imports, tree-shake) ↓ Transpile (SWC - ES6+ → ES5) ↓ Minify (SWC - production build) ↓ Deploy (6-8KB script to device) Rollup bundles everything into a single file and removes unused code (tree-shaking). Builds take ~100ms.\nSWC handles both transpiling (ES6+ → ES5) and minification. It\u0026rsquo;s 20x faster than Babel and good enough for this.\nJSDoc for types instead of TypeScript - I can write vanilla JavaScript and still get type checking. The TypeScript compiler checks JSDoc comments without compiling anything. Less tooling, good enough.\nProject Structure src/ ├── power-monitor/ # Reusable monitoring strategies │ ├── runDurationCheckStrategy.js │ ├── runFrequencyCheckStrategy.js │ ├── powerSignatureCheckStrategy.js │ └── activityExporter.js ├── alert/ # Alert implementations │ ├── compositeAlertStrategy.js │ ├── pagerdutyAlertStrategy.js │ └── mqttAlertStrategy.js ├── common/ # Shared utilities │ ├── logger.js │ └── utils.js ├── ost/ # Location-specific deployments │ └── basement-pumps/ │ ├── main.js # Entry point │ └── config.js # Device-specific config └── types/ # TypeScript definitions ├── shelly.d.ts # Shelly device APIs └── types.d.ts # Application types dist/ └── ost/ └── basement-pumps/ ├── basement-pumps-v0.0.54.js # Dev build (19KB) └── basement-pumps-v0.0.54.min.js # Production (8KB) I organize by physical location (ost/ is somewhere, and furu/ somewhere else). Monitoring strategies like runDurationCheckStrategy.js are reusable across devices - same code could works for different pumps, just different config.\nConfig files rollup.config.js (build setup) import resolve from \u0026#39;@rollup/plugin-node-resolve\u0026#39;; import { swc } from \u0026#39;rollup-plugin-swc3\u0026#39;; import replace from \u0026#39;@rollup/plugin-replace\u0026#39;; const baseConfig = (input, minify = false) =\u0026gt; ({ input: input, output: { file: `dist/${name}.${minify ? \u0026#39;min.\u0026#39; : \u0026#39;\u0026#39;}js`, format: \u0026#39;cjs\u0026#39;, strict: false, // Shelly doesn\u0026#39;t support strict mode }, plugins: [ resolve(), // Resolve node_modules imports swc({ jsc: { parser: { syntax: \u0026#34;ecmascript\u0026#34; }, minify: minify ? { compress: { unused: true }, mangle: true } : {}, target: \u0026#39;es5\u0026#39;, // Critical: ES5 for Espruino! }, }), replace({ values: { \u0026#39;process.env.NODE_ENV\u0026#39;: JSON.stringify( minify ? \u0026#39;production\u0026#39; : \u0026#39;development\u0026#39; ), }, preventAssignment: true, }), ], treeshake: true, // Remove unused code }); export default [ baseConfig(\u0026#39;src/ost/basement-pumps/main.js\u0026#39;, false), // Dev baseConfig(\u0026#39;src/ost/basement-pumps/main.js\u0026#39;, true), // Prod ]; package.json Scripts { \u0026#34;scripts\u0026#34;: { \u0026#34;tsc\u0026#34;: \u0026#34;tsc\u0026#34;, // Type check with TypeScript \u0026#34;lint\u0026#34;: \u0026#34;jshint src\u0026#34;, // Lint JavaScript \u0026#34;test\u0026#34;: \u0026#34;node --test\u0026#34;, // Run tests \u0026#34;prebuild\u0026#34;: \u0026#34;npm run lint \u0026amp;\u0026amp; npm run tsc \u0026amp;\u0026amp; npm run test\u0026#34;, \u0026#34;build\u0026#34;: \u0026#34;rollup -c\u0026#34; // Bundle and minify } } Type Checking with JSDoc Instead of TypeScript files, we use JSDoc comments:\n/** * @param {RunDurationCheckConfig} config * @param {Logger} logger * @param {AlertStrategy} alertStrategy * @return {RunDurationCheckStrategy} */ export function buildRunDurationCheckStrategy(config, logger, alertStrategy) { // TypeScript checks this! return { handler: function(event) { /* ... */ }, periodicChecker: function() { /* ... */ }, run: function() { /* ... */ } }; } Type definitions in src/types/types.d.ts:\ndeclare interface RunDurationCheckConfig { device: string; maxRunDuration: number; pwThreshold: number; eventFilter: (event: any) =\u0026gt; boolean; } declare interface RunDurationCheckStrategy { handler: (event: any, user_data: any) =\u0026gt; void; periodicChecker: () =\u0026gt; void; run: () =\u0026gt; void; } The TypeScript compiler checks all this without compiling - just validation.\nWriting modular code You write normal ES6 imports:\n// src/alert/pagerdutyAlertStrategy.js export default { init: function() { /* ... */ }, alert: function(message) { /* ... */ } }; // src/ost/basement-pumps/main.js import pagerDutyAlertStrategy from \u0026#39;../../alert/pagerdutyAlertStrategy.js\u0026#39;; import { buildRunDurationCheckStrategy } from \u0026#39;../../power-monitor/runDurationCheckStrategy.js\u0026#39;; // Use imports normally compositeAlertStrategy.addStrategy(pagerDutyAlertStrategy); let strategy = buildRunDurationCheckStrategy(config, logger, alertStrategy); Rollup bundles all imports into a single file, converts to ES5, and you get something like:\nvar pagerDutyAlertStrategy = { init: function() { /* ... */ } }; function buildRunDurationCheckStrategy(config, logger, alertStrategy) { /* ... */ } compositeAlertStrategy.addStrategy(pagerDutyAlertStrategy); var strategy = buildRunDurationCheckStrategy(config, logger, alertStrategy); One file, ES5 syntax, ready to paste into Shelly.\nHow well does this work? My basement pump monitoring script builds to:\nBuild Type Size % of 25KB Budget Source (ES6+) 19,663 bytes 79% Dev build (ES5) 19,663 bytes 79% Production (minified) 8,141 bytes 33% ✅ 3.2x compression ratio. Not bad.\nWhat gets smaller Variable names:\n// Source: let currentWindowActiveTime = 0; let previousWindowActiveTime = 0; // Minified: var i=0,o=0; Debug logging removed:\n// Source: logger.debug(\u0026#34;Pump started: \u0026#34; + power + \u0026#34;W\u0026#34;); // Production (logger.debug → noop): // (completely removed by dead code elimination) Function inlining:\n// Source (multiple files): import logger from \u0026#39;./logger.js\u0026#39;; logger.info(\u0026#34;Starting\u0026#34;); // Minified: print(\u0026#34;Starting\u0026#34;); // Inlined to Shelly\u0026#39;s print() What this gets you Local development - Write code in vim, type checking catches errors before deploy, no need to test on device until it\u0026rsquo;s ready.\nReusable code - Same monitoring strategy works for both pumps, just different config. Write once, configure many times.\nActually fits in 25KB - Without minification I\u0026rsquo;d be at 79% of budget already. With it, 33%. Plenty of room.\nDownsides No npm packages - Most are too big or use Node.js APIs. I just write small utilities when needed.\nNo async/await - ES5 doesn\u0026rsquo;t have it. Shelly APIs use callbacks anyway, so not a big deal.\nBuild step required - Can\u0026rsquo;t edit on device directly anymore. Worth it though.\nSource maps don\u0026rsquo;t work - Shelly can\u0026rsquo;t use them. I keep the dev build (non-minified) around for debugging if needed.\n** We need to keep in mind we\u0026rsquo;re aiming to produce espruino compatible javascript, so not every js feature will work*\nSetting this up yourself Install the build tools:\nnpm install --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-replace rollup-plugin-swc3 typescript jshint Create type definitions (src/types/shelly.d.ts):\ndeclare const Shelly: { call: (method: string, params: any, callback: Function) =\u0026gt; void; addStatusHandler: (handler: Function) =\u0026gt; number; emitEvent: (event: string, data: any) =\u0026gt; void; }; declare const Timer: { set: (interval: number, repeat: boolean, callback: Function) =\u0026gt; number; }; Configure TypeScript (tsconfig.json):\n{ \u0026#34;compilerOptions\u0026#34;: { \u0026#34;allowJs\u0026#34;: true, \u0026#34;checkJs\u0026#34;: true, \u0026#34;noEmit\u0026#34;: true, \u0026#34;target\u0026#34;: \u0026#34;ES5\u0026#34;, \u0026#34;module\u0026#34;: \u0026#34;ES2015\u0026#34; }, \u0026#34;include\u0026#34;: [\u0026#34;src/**/*\u0026#34;] } Write a script (src/main.js):\n/** * @param {string} message */ function logMessage(message) { print(\u0026#34;LOG: \u0026#34; + message); } logMessage(\u0026#34;Hello Shelly!\u0026#34;); Timer.set(60000, true, function() { logMessage(\u0026#34;Tick\u0026#34;); }); Build and deploy:\nnpm run build # Then copy/paste dist/main.min.js to Shelly web UI My deployment workflow I use SourceHut\u0026rsquo;s build pipeline - it runs the build on every push (incredibly fast, you should try it) and I can download the artifact from there. Then I just paste it into the Shelly web UI.\nFor quick iteration I sometimes do:\nnpm run build \u0026amp;\u0026amp; cat dist/ost/basement-pumps/basement-pumps-v0.0.56.js | wl-copy Then paste in the web UI. Not elegant but works.\nReal example The basement pump monitoring script I mentioned:\nMonitors 2 sump pumps (power consumption, runtime, frequency) Sends PagerDuty alerts when something\u0026rsquo;s wrong Exports metrics to Grafana every minute 3 detection layers (power signature in 90s, duration in 2hr, frequency over 24hr) Final size: 8,141 bytes (33% of 25KB budget) Timers used: 1 of 5 Build time: ~100ms\nSource has comments, types, multiple files with imports. Build output is one minified ES5 file. Under a second to build.\nWrapping up The build pipeline setup takes maybe an hour. After that, you can write maintainable code with type safety and actually iterate quickly. The minification means you can write readable code and still fit in 25KB.\nFor simple scripts (blink an LED, turn on a relay at sunset), this is overkill. But once you\u0026rsquo;re doing anything non-trivial - monitoring, complex logic, reusable components - the tooling pays off fast.\nThe next post covers how I used this setup to build a stuck pump detector that went from 2-hour detection to 90-second alerts by analyzing power consumption patterns in Grafana.\nResources Shelly API docs Espruino reference Rollup , SWC ","permalink":"https://mikael.srht.site/posts/2025-10-25_shelly_script_build_setup/","tags":["shelly","javascript","iot","embedded","rollup","espruino"],"title":"Building Production Shelly Scripts with Modern JavaScript"},{"categories":null,"content":"After experiencing multiple complete system freezes over two months on Arch Linux, I traced the issue to OOM (Out of Memory) events. The system would become completely unresponsive - no mouse, no keyboard, forcing hard reboots.\nRoot Cause Checking journalctl revealed the smoking gun:\njournalctl -b -1 --no-pager | grep -i \u0026#34;oom\u0026#34; The OOM killer had terminated systemd-journald and user processes. With 27GB RAM + 16GB swap, memory exhaustion shouldn\u0026rsquo;t happen often, but when it does, the kernel freezes everything before killing processes.\nThe Fixes 1. Install earlyoom Early OOM daemon kills memory-hungry processes before total freeze:\nsudo pacman -S earlyoom sudo systemctl enable --now earlyoom 2. Enable Magic SysRq Keys Emergency recovery when frozen. Create the config:\necho \u0026#34;kernel.sysrq = 1\u0026#34; | sudo tee /etc/sysctl.d/99-sysrq.conf sudo sysctl -p /etc/sysctl.d/99-sysrq.conf When frozen: Hold Alt + SysRq (Print Screen) then slowly type REISUB\nR: Switch keyboard from raw mode E: Terminate all processes I: Kill all processes S: Sync filesystems U: Remount read-only B: Reboot 3. Reduce Swappiness Lower swap usage for better performance:\necho \u0026#34;vm.swappiness = 10\u0026#34; | sudo tee /etc/sysctl.d/99-swappiness.conf echo \u0026#34;vm.vfs_cache_pressure = 50\u0026#34; | sudo tee -a /etc/sysctl.d/99-swappiness.conf sudo sysctl -p /etc/sysctl.d/99-swappiness.conf 4. Enable systemd-oomd Additional OOM protection layer:\nsudo systemctl enable --now systemd-oomd Monitoring Use htop to watch memory usage. Sort by memory with Shift+M. In my case, WinBox was consuming 1GB and needed periodic restarts.\nResult System now has multiple layers of protection:\nearlyoom intervenes before total freeze systemd-oomd provides backup protection Magic SysRq allows safe emergency reboot Reduced swappiness improves responsiveness No freezes since implementing these changes.\n","permalink":"https://mikael.srht.site/2025-10/arch-oom-fixes/","tags":null,"title":"Fixing Arch Linux System Freezes"},{"categories":null,"content":" title: \u0026ldquo;Self-Hosting MyBibliotheca: A Family Reading Tracker Without Docker\u0026rdquo; date: 2025-08-09 description: \u0026ldquo;Complete guide to installing MyBibliotheca on a Debian NAS with systemd, nginx reverse proxy, and maximum security hardening - no Docker required.\u0026rdquo; tags: [\u0026ldquo;self-hosting\u0026rdquo;, \u0026ldquo;books\u0026rdquo;, \u0026ldquo;family\u0026rdquo;, \u0026ldquo;debian\u0026rdquo;, \u0026ldquo;nginx\u0026rdquo;, \u0026ldquo;systemd\u0026rdquo;] categories: [\u0026ldquo;tutorials\u0026rdquo;, \u0026ldquo;self-hosting\u0026rdquo;] Warning : this post has been generated by claude llm, after installing it myself going through trial and errors.\nLooking for a simple, privacy-focused way to track your family\u0026rsquo;s reading habits? MyBibliotheca is an excellent self-hosted alternative to Goodreads that\u0026rsquo;s perfect for families. Unlike most guides that rely on Docker, this tutorial shows you how to install it directly on Debian with some security hardening.\nWhy MyBibliotheca? MyBibliotheca offers exactly what families need:\nISBN scanning for easy book addition Reading progress tracking with visual streaks Clean, kid-friendly interface without social media complexity Parent monitoring through shared web access Privacy-first - your data stays on your server Prerequisites Debian-based server/NAS Nginx with reverse proxy capability Basic command line familiarity Domain name or subdomain for access Step 1: Create Dedicated System User Create a secure, isolated user for the application:\n# Create system user with home directory sudo useradd --system --create-home --shell /bin/bash mybibliotheca Step 2: Install Application Note : I used asdf to install a local version of python (python 3.13.6), which solved some issue with pip install\n# Switch to the new user sudo su - mybibliotheca # Clone the repository git clone https://github.com/pickles4evaaaa/mybibliotheca.git cd mybibliotheca # Create virtual environment python3 -m venv venv source venv/bin/activate # Install dependencies pip install -r requirements.txt # Install gunicorn for production serving pip install gunicorn # Setup data directory and database python3 setup_data_dir.py # Test the installation (optional) gunicorn -w 2 -b 127.0.0.1:5054 run:app # Press Ctrl+C to stop after confirming it works # Exit back to root user exit Step 3: Create Systemd Service Create the service file with maximum security hardening:\nsudo nano /etc/systemd/system/mybibliotheca.service Add this configuration:\n[Unit] Description=MyBibliotheca Personal Library Tracker After=network.target [Service] Type=simple User=mybibliotheca Group=mybibliotheca WorkingDirectory=/home/mybibliotheca/mybibliotheca Environment=PATH=/home/mybibliotheca/mybibliotheca/venv/bin Environment=VIRTUAL_ENV=/home/mybibliotheca/mybibliotheca/venv Environment=TIMEZONE=Europe/Helsinki ExecStart=/home/mybibliotheca/mybibliotheca/venv/bin/gunicorn -w 2 -b 127.0.0.1:5054 run:app Restart=always RestartSec=10 StandardOutput=journal StandardError=journal # Maximum security hardening NoNewPrivileges=true PrivateTmp=true ProtectKernelTunables=true ProtectKernelModules=true ProtectControlGroups=true RestrictSUIDSGID=true RestrictRealtime=true MemoryDenyWriteExecute=true SystemCallArchitectures=native ProtectSystem=strict ReadWritePaths=/home/mybibliotheca/mybibliotheca/data PrivateDevices=true ProtectHostname=true ProtectClock=true [Install] WantedBy=multi-user.target Step 4: Enable and Start Service # Reload systemd daemon sudo systemctl daemon-reload # Enable service to start on boot sudo systemctl enable mybibliotheca # Start the service sudo systemctl start mybibliotheca # Verify it\u0026#39;s running sudo systemctl status mybibliotheca Step 5: Configure Nginx Reverse Proxy For a subdomain setup, create a new nginx configuration:\nsudo nano /etc/nginx/sites-available/mybibliotheca Add this configuration (replace books.yourdomain.com with your subdomain):\nserver { listen 80; server_name books.yourdomain.com; location / { proxy_pass http://127.0.0.1:5054; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Handle WebSocket connections proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; # Increased timeouts for book metadata fetching proxy_read_timeout 300; proxy_connect_timeout 300; proxy_send_timeout 300; } } Enable the site and reload nginx:\n# Enable the site sudo ln -s /etc/nginx/sites-available/mybibliotheca /etc/nginx/sites-enabled/ # Test nginx configuration sudo nginx -t # Reload nginx sudo systemctl reload nginx Step 6: SSL Certificate (Optional but Recommended) If you\u0026rsquo;re using Let\u0026rsquo;s Encrypt with certbot:\nsudo certbot --nginx -d books.yourdomain.com Initial Setup Navigate to https://books.yourdomain.com in your browser Complete the one-time setup form: Choose an admin username Provide an admin email Set a secure password Start adding books by ISBN! Management and Maintenance Service Management # Check service status sudo systemctl status mybibliotheca # View real-time logs sudo journalctl -u mybibliotheca -f # Restart service sudo systemctl restart mybibliotheca Updates # Switch to application user sudo su - mybibliotheca cd mybibliotheca # Backup database first cp data/books.db data/books.db.backup # Pull updates git pull # Activate virtual environment and update dependencies source venv/bin/activate pip install -r requirements.txt # Exit back to root exit # Restart service sudo systemctl restart mybibliotheca Backup Strategy # Simple backup command (add to cron for automation) sudo cp /home/mybibliotheca/mybibliotheca/data/books.db /your-backup-location/books-$(date +%Y%m%d).db Security Features This setup provides enterprise-grade security hardening:\nProcess isolation with dedicated system user Filesystem protection with read-only system access Network isolation bound only to localhost Kernel protection against privilege escalation Memory protection against code injection Device isolation from physical hardware Resource Usage MyBibliotheca is reasonably lightweight:\nRAM: 164 MB ps -u mybibliotheca --no-headers -o rss | awk \u0026#39;{sum+=$1} END {print sum/1024 \u0026#34; MB\u0026#34;}\u0026#39; 163.703 MB Storage: \u0026lt;100MB total CPU: Minimal (only during page loads) Family-Friendly Features ISBN scanning: Kids just type the barcode number Visual progress: Book covers and reading streaks Simple interface: No social features to confuse young users Parent visibility: Monitor all reading activity Monthly wrap-ups: Generate shareable reading achievements Conclusion (edited) MyBibliotheca might be a good fit for my needs. I\u0026rsquo;m happy with this bare metal approach, instead of docker which has betrayed me more than once\n","permalink":"https://mikael.srht.site/2025-08/mybibliotheca/","tags":null,"title":"2025 08 09_mybibliotheca"},{"categories":null,"content":"This memo is about running a systemd service isolated in a network namespace on linux, inside which a vlan interface has been moved.\nNetwork setup Create namespace sudo ip netns add torrentns Create the vlan interface /etc/network/interfaces auto enp5s0.33 iface enp5s0.33 inet manual post-up /root/bin/move-to-namespace enp5s0.33 helper script #!/bin/bash # Usage: move-to-namespace \u0026lt;iface\u0026gt; set -e iface=\u0026#34;$1\u0026#34; ns=\u0026#34;torrentns\u0026#34; # Create namespace if not exists if ! ip netns list | grep -q \u0026#34;^$ns\u0026#34;; then ip netns add \u0026#34;$ns\u0026#34; fi # Move interface into namespace ip link set \u0026#34;$iface\u0026#34; netns \u0026#34;$ns\u0026#34; # Bring up interfaces inside namespace ip netns exec \u0026#34;$ns\u0026#34; ip link set lo up ip netns exec \u0026#34;$ns\u0026#34; ip link set \u0026#34;$iface\u0026#34; up # Start DHCP client inside namespace ip netns exec \u0026#34;$ns\u0026#34; dhclient -v \u0026#34;$iface\u0026#34; # Optional: Set up DNS mkdir -p /etc/netns/$ns echo \u0026#34;nameserver 1.1.1.1\u0026#34; \u0026gt; /etc/netns/$ns/resolv.conf Activation run : sudo ifup enp5s0.33\ncheck : sudo ip netns exec /bin/bash ip a\nService setup Systemd will take care of starting the service on boot. Contrary to the default service definition, we\u0026rsquo;ll start with root to start the service inside the namespace, and rely on sudo to drop permissions.\nsudo systemctl edit transmission-daemon\n[Service] # Clear the original ExecStart ExecStart= ExecStart=/usr/bin/ip netns exec torrentns /usr/bin/sudo -u debian-transmission /usr/bin/transmission-daemon -f --log-error # Ensure the service runs as root to enter the namespace User=root Group=root # Drop dangerous capabilities and tighten security CapabilityBoundingSet= NoNewPrivileges=true ProtectSystem=full ProtectHome=yes PrivateTmp=yes ","permalink":"https://mikael.srht.site/2025-06/vlan-bound-service-inside-network-namespace/","tags":null,"title":"vlan bound service isolated in network namespace"},{"categories":null,"content":"My first vlan setup brought isolated networks for both wired and wireless clients, at the cost of having a dedicated ssid per vlan.\nAs each ssid takes a bit of bandwidth, this didn\u0026rsquo;t scale and as I renewed some of my equipment, I took the opportunity to try something else :\nssid1: 802.1x with mikrotik user-manager as radius server to authenticate and assign vlans\nssid2: wpa2-psk with mikrotik user-manager in the background to assign per mac address vlans\nOnly use uci or editing /etc/config/wireless. Do not edit this with Luci afterwards or you\u0026rsquo;ll risk loosing some settings.\nconfig wifi-iface \u0026#39;default_radio0\u0026#39; option device \u0026#39;radio0\u0026#39; option mode \u0026#39;ap\u0026#39; option ssid \u0026#39;ssid1\u0026#39; option encryption \u0026#39;wpa2\u0026#39; option dynamic_vlan \u0026#39;2\u0026#39; option vlan_bridge \u0026#39;br-lan\u0026#39; option network \u0026#39;vlan101\u0026#39; option auth_server \u0026#39;radius-ip\u0026#39; option auth_port \u0026#39;1812\u0026#39; option auth_secret \u0026#39;radius-secret\u0026#39; option vlan_tagged_interface \u0026#39;eth0\u0026#39; config wifi-iface \u0026#39;wifinet1\u0026#39; option device \u0026#39;radio0\u0026#39; option mode \u0026#39;ap\u0026#39; option ssid \u0026#39;ssid2\u0026#39; option encryption \u0026#39;psk2\u0026#39; option key \u0026#39;ssid-password\u0026#39; option dynamic_vlan \u0026#39;2\u0026#39; option vlan_bridge \u0026#39;br-lan\u0026#39; option vlan_tagged_interface \u0026#39;eth0\u0026#39; option hostapd_options \u0026#39;macaddr_acl=2\u0026#39; option auth_server \u0026#39;192.168.99.1\u0026#39; option auth_port \u0026#39;1812\u0026#39; option auth_secret \u0026#39;radius-secret\u0026#39; option server \u0026#39;radius-ip\u0026#39; option port \u0026#39;1812\u0026#39; option hostapd_options \u0026#39;macaddr_acl=2\u0026#39; ","permalink":"https://mikael.srht.site/2025-05/openwrt-dynamic-vlans/","tags":null,"title":"OpenWrt AP with dynamic vlans"},{"categories":null,"content":"I am slowly bringing floor heating to some rooms, and decided I would use a kincony a8 board to pilot the valves for the different circuits.\nSo I got this board and was not sure where to start. I found this link which was simple enough to give it a try.\nIt worked nicely, although this was actually aimed at esphome integration, which is not (yet) what I want. I would rather see how far I can get with an isolated solution, not depending on my homeassistant setup.\nContinued my readings and found these firmwares and these command line instructions to work\nesptool.py --chip esp32 -p /dev/ttyUSB0 -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 40m --flash_size 2MB 0x0000 ~/Downloads/KCS_KC868_A8_V2.2.10.bin That brought Kincony\u0026rsquo;s own firmware. Good to have as a starting point to explore possibilities, but it seems there\u0026rsquo;s a caveat: it doesn\u0026rsquo;t support multiple ds18b20 sensors on a single wire.\nSo next step, tasmota. There\u0026rsquo;s a reference thread with a video presentation on the kincony forum here but I stumbled on the same issue as a person going under username Vulcan described here What worked :\ndownload tasmota32.factory.bin and flash that first (esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x0 tasmota32.factory.bin) once logged into the AP, upgrade to tasmota from kincony figure out version is old and I need to compile (instructions ) a new version myself use python 3.11.0 (with asdf) to please platformio then apply these settings in tasmota/user_config_override.h\n#undef USE_DOMOTICZ #undef USE_KNX #undef USE_MATTER #undef USE_MATTER_DEVICE #ifndef USE_I2C #define USE_I2C // I2C using library wire (+10k code, 0k2 mem, 124 iram) #endif #define USE_PCF8574 // [I2cDriver2] Enable PCF8574 I/O Expander (I2C addresses 0x20 - 0x26 and 0x39 - 0x3F) (+1k9 code) #define USE_PCF8574_SENSOR // enable PCF8574 inputs and outputs in SENSOR message #define USE_PCF8574_DISPLAYINPUT // enable PCF8574 inputs display in Web page #define USE_PCF8574_MQTTINPUT // enable MQTT message \u0026amp; rule process on input change detection : stat/%topic%/PCF8574_INP = {\u0026#34;Time\u0026#34;:\u0026#34;2021-03-07T16:19:23+01:00\u0026#34;,\u0026#34;PCF8574-1_INP\u0026#34;:{\u0026#34;D1\u0026#34;:1}} #define USE_ETHERNET // Add support for ethernet (Currently fixed for Olimex ESP32-PoE) #define ETH_TYPE 0 // [EthType] 0 = ETH_PHY_LAN8720, 1 = ETH_PHY_TLK110, 2 = ETH_PHY_IP101 #define ETH_ADDR 0 // [EthAddress] 0 = PHY0 .. 31 = PHY31 #define ETH_CLKMODE 3 // [EthClockMode] 0 = ETH_CLOCK_GPIO0_IN, 1 = ETH_CLOCK_GPIO0_OUT, 2 = ETH_CLOCK_GPIO16_OUT, 3 = ETH_CLOCK_GPIO17_OUT #define USE_RC_SWITCH // Add support for RF transceiver using library RcSwitch (+2k7 code, 460 iram) #define USE_RF_SENSOR // Add support for RF sensor receiver (434MHz or 868MHz) (+0k8 code) #define USE_THEO_V2 // Add support for decoding Theo V2 sensors as documented on https://sidweb.nl using 434MHz RF sensor receiver (+1k4 code) #define USE_ALECTO_V2 // Add support for decoding Alecto V2 sensors like ACH2010, WS3000 and DKW2012 weather stations using 868MHz RF sensor receiver (+1k7 code) #define USE_BLE_ESP32 // Enable BLE on ESP32 - needs at least 2M flash // #define USE_THERMOSTAT TODO: wire sensors and use setthings from https://tasmota.github.io/docs/Thermostat/ #endif // _USER_CONFIG_OVERRIDE_H_ platformio run -e tasmota32 through the web ui, upgrade the firmware found in build_outputs/firmware (need to go through factory first, and regular firmware after) ","permalink":"https://mikael.srht.site/2025-05/kincony-a8-initial-setup/","tags":null,"title":"Kincony A8 initial setup"},{"categories":null,"content":"Setting up a minimal configuration on a Mikrotik device sometime requires a subtle sequence where access method changes so as to avoid cutting the branch you\u0026rsquo;re sitting on.\nLayer 2 connection I\u0026rsquo;ve often found mactelnet to work when winbox would not, so don\u0026rsquo;t forget to give it a try.\nsudo mactelnet AA::BB:CC:DD:EE -n -u admin -p \u0026#34;\u0026#34; Wifi connection Sometime a wifi setup can give a more stable connection when changing vland and bridge settings :\nThen a temporary wifi setup\n/interface bridge add name=bridge /interface bridge port add bridge=bridge interface=wifi1 /ip address add address=192.168.88.1/24 interface=bridge network=192.168.88.0 /interface wifi set [ find default-name=wifi1 ] configuration.mode=ap .ssid=capax disabled=no security.authentication-types=wpa2-psk security.passphrase=mikrotik /ip pool add name=dhcp_pool0 ranges=192.168.88.2-192.168.88.254 /ip dhcp-server add address-pool=dhcp_pool0 interface=bridge name=dhcp1 relay=192.168.88.1 /ip dhcp-server network add address=192.168.88.0/24 dns-server=1.1.1.1 gateway=192.168.88.1 minimal CAP setup https://help.mikrotik.com/docs/spaces/ROS/pages/224559120/WiFi#WiFi-CAPusing%22wifi-qcom%22package%3A /interface bridge add name=bridgeLocal /interface wifi datapath add bridge=bridgeLocal comment=defconf disabled=no name=capdp /interface wifi set [ find default-name=wifi1 ] configuration.manager=capsman datapath=capdp disabled=no set [ find default-name=wifi2 ] configuration.manager=capsman datapath=capdp disabled=no /interface bridge port add bridge=bridgeLocal comment=defconf interface=ether1 add bridge=bridgeLocal comment=defconf interface=ether2 add bridge=bridgeLocal comment=defconf interface=ether3 add bridge=bridgeLocal comment=defconf interface=ether4 add bridge=bridgeLocal comment=defconf interface=ether5 /interface wifi cap set discovery-interfaces=bridgeLocal enabled=yes slaves-datapath=capdp /ip dhcp-client add interface=bridgeLocal disabled=no Observation:\nOne thing to note here is that there\u0026rsquo;s no vlan setup, and the dhcp-client will therefore use untagged packets. If you use switch based vlans on the other end, you should thus set the switch port to use a proper default vlan-id and use \u0026ldquo;add if missing\u0026rdquo; for the \u0026ldquo;vlan header\u0026rdquo;\nAlternatively, you could specify the use of vlan on the cap side like that :\n/interface vlan add interface=bridge name=vlan1-main vlan-id=99 /interface wireless cap set bridge=bridge discovery-interfaces=vlan1-main enabled=yes interfaces=wlan1 /ip dhcp-client add interface=vlan1-main Then the other end should have \u0026ldquo;vlan header\u0026rdquo; set as \u0026ldquo;leave as is\u0026rdquo;, without any default vlan-id\n","permalink":"https://mikael.srht.site/posts/2024-10-28_mikrotik_cap_setup/","tags":null,"title":"Mikrotik CAP setup"},{"categories":null,"content":"Context cheap noname adapter with EFR32MG21 chip\ncomes with a deprecated (from zigbee2mqtt\u0026rsquo;s pov) firmware that works with this setup :\nserial: port: /dev/zigbee2mqtt # Use the correct serial port (or create a udev rule for a persistent symlink) adapter: ezsp # Specify the new EFR32MG21 adapter (EZSP protocol) However, zigbee2mqtt logs about deprecation and missing features. Was this a waster of money on a doomed electronic crap ? Maybe not\u0026hellip;\nupdate :\nweb flashing worked ! I used \u0026ldquo;ZB-GW04 (v1.1) Easyiot ZB-GW04 Revision v1.1 - No flow control\u0026rdquo;\nhad to do it again by manually uploading firmware from the silabs firmware builder repo Now time will tell if this was worth the money or not\u0026hellip;\nudev rule memo udevadm info -a -n /dev/ttyUSB0 | grep \u0026#39;{idVendor}\u0026#39; | head -n1 udevadm info -a -n /dev/ttyUSB0 | grep \u0026#39;{idProduct}\u0026#39; | head -n1 /etc/udev/rules.d/99-usb-serial.rules:\nSUBSYSTEM==\u0026#34;tty\u0026#34;, ATTRS{idVendor}==\u0026#34;1a86\u0026#34;, ATTRS{idProduct}==\u0026#34;7523\u0026#34;, SYMLINK+=\u0026#34;zigbee2mqtt\u0026#34; sudo udevadm control --reload-rules \u0026amp;\u0026amp; sudo udevadm trigger ","permalink":"https://mikael.srht.site/posts/2024-10-19_zigbee-adapter/","tags":null,"title":"EFR32MG21 Zigbee Adapter"},{"categories":null,"content":" pigallery2 used the source set node version with asdf /opt/pigallery2 :\nasdf local nodejs 18.20.0\nAnd edited the systemd service to load asdf\nExecStart=/bin/bash -c \u0026#39;. /home/mikael/.asdf/asdf.sh \u0026amp;\u0026amp; npm start\u0026#39; WorkingDirectory=/opt/pigallery2 phockup : follow the doc at https://github.com/ivandokov/phockup But also do like this:\npython -m venv . source bin/activate change shebang towards #!/opt/phockup/bin/python3\n","permalink":"https://mikael.srht.site/posts/2024-09-15-pigallery/","tags":null,"title":"2024 09 15 Pigallery"},{"categories":null,"content":"read the docs : https://www.home-assistant.io/installation/linux stop service\nwith same python version\n1cd homeassistant 2source bin/activate 3pip install --upgrade homeassistant with new python version 1mv homeassistant homeassistant-oldpython 2 3mkdir homeassistant \u0026amp; cd homeassistant 4asdf install python 3.12.5 5asdf local python 3.12.5 6 7python -m venv . 8source bin/activate 9pip install homeassistant ","permalink":"https://mikael.srht.site/posts/2024-08-24-update-home-assistant/","tags":null,"title":"Home Assistant update notes"},{"categories":null,"content":" stop services (systemctl | grep zwave)\ndo the update\n1cd zwave-js-server 2 3git pull origin master 4 5npm install restart services ","permalink":"https://mikael.srht.site/posts/2024-08-24-update-zwave-js-server/","tags":null,"title":"2024 08 24 Update Zwave Js Server"},{"categories":null,"content":" go to /var/www/monica and read the doc at docs/installation/update.md\nopen a shell a www-data\n1sudo su www-data -s /bin/bash fetch latest branches 1git fetch -a follow the rest of the documentation\u0026hellip; ","permalink":"https://mikael.srht.site/posts/2024-08-24-update-monica/","tags":null,"title":"2024 08 24 Update Monica"},{"categories":null,"content":"Welcome to my collection of technical notes (low effort dump of stuff I would otherwise forget)\n","permalink":"https://mikael.srht.site/about/","tags":null,"title":"About"},{"categories":null,"content":"","permalink":"https://mikael.srht.site/search/_index.es/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://mikael.srht.site/search/_index.fr/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://mikael.srht.site/search/_index.hi/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://mikael.srht.site/search/_index.jp/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://mikael.srht.site/search/_index.pl/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://mikael.srht.site/search/_index.ru/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://mikael.srht.site/search/_index.zh-cn/","tags":null,"title":""}]