[{"content":"","date":"27 July 2026","externalUrl":null,"permalink":"/tags/container/","section":"Tags","summary":"","title":"Container","type":"tags"},{"content":"","date":"27 July 2026","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"","date":"27 July 2026","externalUrl":null,"permalink":"/","section":"Eren Eroğlu","summary":"","title":"Eren Eroğlu","type":"page"},{"content":" What is Syncthing # Syncthing is a great application to sync files between your devices. It always worked for me without setting up any infrastructure myself and I have been using it for a while to sync my music collection, books and KeePassXC database. While it does not require me to setup any infrastructure it needs some infrastructure to be set up. Syncthing at bare minimum needs to discover the IP addresses of peers. If two peers are in the same broadcast domain, syncthing can discover peers using local discovery methods. But if peers are in different broadcast domains, they are discovered using global discovery methods. This depend on the community hosted discovery servers. Upon discovering their IP addresses, peers try to connect directly. If not this is where you have to abandon all hope.\nA little rant about Internet Services # Because the world is a cruel place, most ISP\u0026rsquo;s in my country are incompetent at best and do not provide IPv6 to their customers. Even my university (METU) has problems with IPv6 addresses. These ISP\u0026rsquo;s (which in my opinion very much fail to provide internet service) make sure to assign minimal amount of IP addresses to their customers by using one or more layers of NAT. In other parts of the world many ISP\u0026rsquo;s can not provide addresses because of IPv4 exhaustion but fear not, this isn\u0026rsquo;t the case for me. In my country, ISP\u0026rsquo;s is actually did not assign most of their IP space to anybody and they refuse it because of the monetary incentives. Why should they care about the internet and the end to end connectivity and all the other things when they can charge more to use some arbitrary numbers ?\nHow Syncthing works when there is nothing to connect # Syncthing tries to overcome these limitations by trying multiple NAT traversal techniques. These are mostly successful in my case but still there are some cases where connectivity can not be established. When NAT traversal fails syncthing tries to relay the data from community hosted relay servers.\nSo what ? # I have benefited from these relay servers and I want to contribute back. I have realized my 3 VPS servers are mostly sitting idle and I will host syncthing relays on these servers. Setting these servers are actually surprisingly easy. I am using Debian 13, but you can follow along with any distribution. I will use podman with rootless containers in separate users and I want to automatically start and update the syncthing relay containers. Podman integrates nicely with Systemd.\nThe Setup # First I have installed the dependencies with:\nsudo apt install podman systemd-container Then I created a user account:\nsudo useradd -F -r -s /usr/sbin/nologin -m -d /var/lib/syncthing syncthing Since I want the service to be active all the times I enabled lingering which keeps a systemd user session intact. Normally user sessions are created upon PAM authentication they are active only when someone is actively using the account. But in this case lingering is what I want:\nsudo loginctl enable-linger syncthing Now I want to open a shell as the new syncthing user but I want this shell to inherit all the necessary environment variables from systemd user session. The systemd-container package provides machinectl utility. Normally this is used for managing systemd containers, but we can also use it open a shell with all the necessary variables for systemd:\nsudo machinectl shell syncthing@ /bin/bash Now the only thing remaining is to write the configuration for syncthing container. I am using a feature of podman called podman quadlets. This feature lets systemd recognize some files to create containers as systemd services on the fly. You can find more information here. The first file defines a volume to hold persistent data. I have placed this in ~syncthing/.config/containers/systemd/syncthing.volume\n[Volume] the next file is for configuring the container. Here is my configuration in ~syncthing/.config/containers/systemd/syncthing.container:\n[Unit] Description=Syncthing Relay Service [Container] Image=docker.io/syncthing/relaysrv:edge AutoUpdate=registry PublishPort=22067:22067 PublishPort=22070:22070 Volume=syncthing.volume:/var/strelaysrv [Service] Restart=always [Install] WantedBy=default.target Now the only thing remains is to instruct systemd to scan the file system to detect newly created services and start the containers:\nsystemctl --user daemon-reload systemctl --user start syncthing We don\u0026rsquo;t need to instruct it to start the services on boot since the podman generator handles it. We need to enable the timer for auto updates though:\nsystemctl --user enable podman-auto-update.timer If everything is done correctly, the relay IP should be on relay statistics page. I have checked and I can see my relay is up. You may need to alter your firewall rules to allow TCP traffic with destination ports 22067 and 22070 but this entirely depends on your setup so I did not include them in this post.\n","date":"27 July 2026","externalUrl":null,"permalink":"/posts/hosting_syncthing_relays/","section":"Posts","summary":"","title":"Hosting Syncthing Relays","type":"posts"},{"content":"","date":"27 July 2026","externalUrl":null,"permalink":"/tags/internet/","section":"Tags","summary":"","title":"Internet","type":"tags"},{"content":"","date":"27 July 2026","externalUrl":null,"permalink":"/tags/nat/","section":"Tags","summary":"","title":"Nat","type":"tags"},{"content":"","date":"27 July 2026","externalUrl":null,"permalink":"/tags/podman/","section":"Tags","summary":"","title":"Podman","type":"tags"},{"content":"","date":"27 July 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"27 July 2026","externalUrl":null,"permalink":"/tags/relay/","section":"Tags","summary":"","title":"Relay","type":"tags"},{"content":"","date":"27 July 2026","externalUrl":null,"permalink":"/tags/syncthing/","section":"Tags","summary":"","title":"Syncthing","type":"tags"},{"content":"","date":"27 July 2026","externalUrl":null,"permalink":"/tags/systemd/","section":"Tags","summary":"","title":"Systemd","type":"tags"},{"content":"","date":"27 July 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"23 July 2026","externalUrl":null,"permalink":"/tags/dovecot/","section":"Tags","summary":"","title":"Dovecot","type":"tags"},{"content":"","date":"23 July 2026","externalUrl":null,"permalink":"/tags/email/","section":"Tags","summary":"","title":"Email","type":"tags"},{"content":"I have recently switched to a self managed email solution. I use Stalwart Mail as my email server and it handles almost all of the email features I can think of perfectly.\nI used to have a postfix + dovecot + opendkim as my email server. To be honest the setup worked perfectly and I had no reason to switch, but I wanted to experiment with this new and shiny email server called Stalwart.\nOne of my complaints compared to my older setup is that in the Stalwart\u0026rsquo;s Web UI, instead of hiding the paid features they chose to put a lock icon besides them. I understand having a paid and a free model. They ofcourse need to be able to fund their work, and it is hard to fund open source, but having that lock icon instead of having nothing sometimes becomes really annoying.\nOne of the features of this email server is that the users can upload their sieve scripts to run on the server side when they recieve an email. This feature also exists in dovecot but I never thought about setting it up mostly because I saw it as a useless feature. I use Thunderbird as my email client, though I am thinking about switching to Neomutt or a JMAP client, and Thunderbird includes client side filtering for mails. From Thunderbird I can move specific mails to their respective folders with message filters. It works but only when I open my Thunderbird on my computer. Sometimes I want to check my emails from my phone but I don\u0026rsquo;t have the filters on the phone. So they all end up in the main mailbox sitting until I open my PC and run the filters on them.\nNow that I switched to Stalwart I can utilize the sieve function to sort these mails in the server side. I am using a very simple script and I will explain it line by line.\nThe whole script is as following:\nrequire [\u0026#34;fileinto\u0026#34;]; if anyof ( header :contains \u0026#34;X-Spam-Flag\u0026#34; \u0026#34;YES\u0026#34;, header :contains \u0026#34;X-Spam-Status\u0026#34; \u0026#34;Yes\u0026#34; ) { stop; } if header :contains \u0026#34;to\u0026#34; \u0026#34;digikey@\u0026lt;my domain\u0026gt;\u0026#34; { fileinto \u0026#34;Digikey\u0026#34;; stop; } if header :contains \u0026#34;to\u0026#34; \u0026#34;farnell@\u0026lt;my domain\u0026gt;\u0026#34; { fileinto \u0026#34;Farnell\u0026#34;; stop; } It is a very simple script. require [\u0026quot;fileinto\u0026quot;]; is what allows me to use fileinto function to move mail between folders. I want spam mails to remain in spam folder so I use\nif anyof ( header :contains \u0026#34;X-Spam-Flag\u0026#34; \u0026#34;YES\u0026#34;, header :contains \u0026#34;X-Spam-Status\u0026#34; \u0026#34;Yes\u0026#34; ) { stop; } this checks if the mails is spam and immediatly terminates the sieve script if it is marked as such. Then the script follows with a number of blocks:\nif header :contains \u0026#34;to\u0026#34; \u0026#34;\u0026lt;service\u0026gt;@\u0026lt;my domain\u0026gt;\u0026#34; { fileinto \u0026#34;\u0026lt;Folder\u0026gt;\u0026#34;; stop; } When I sign up to services I use \u0026lt;service\u0026gt;@\u0026lt;mydomain\u0026gt; as my email so when they send messages these blocks in sieve scripts capture them and put them into their respective folders. I wish I knew this trick when I first started using email but better late then newer.\n","date":"23 July 2026","externalUrl":null,"permalink":"/posts/email_sieving/","section":"Posts","summary":"","title":"Email Sieving","type":"posts"},{"content":"","date":"23 July 2026","externalUrl":null,"permalink":"/tags/imap/","section":"Tags","summary":"","title":"Imap","type":"tags"},{"content":"","date":"23 July 2026","externalUrl":null,"permalink":"/tags/jmap/","section":"Tags","summary":"","title":"Jmap","type":"tags"},{"content":"","date":"23 July 2026","externalUrl":null,"permalink":"/tags/mail/","section":"Tags","summary":"","title":"Mail","type":"tags"},{"content":"","date":"23 July 2026","externalUrl":null,"permalink":"/tags/opendkim/","section":"Tags","summary":"","title":"Opendkim","type":"tags"},{"content":"","date":"23 July 2026","externalUrl":null,"permalink":"/tags/postfix/","section":"Tags","summary":"","title":"Postfix","type":"tags"},{"content":"","date":"23 July 2026","externalUrl":null,"permalink":"/tags/sieve/","section":"Tags","summary":"","title":"Sieve","type":"tags"},{"content":"","date":"23 July 2026","externalUrl":null,"permalink":"/tags/smtp/","section":"Tags","summary":"","title":"Smtp","type":"tags"},{"content":"","date":"23 July 2026","externalUrl":null,"permalink":"/tags/stalwart/","section":"Tags","summary":"","title":"Stalwart","type":"tags"},{"content":"","date":"23 July 2026","externalUrl":null,"permalink":"/tags/thunderbird/","section":"Tags","summary":"","title":"Thunderbird","type":"tags"},{"content":"","date":"10 February 2025","externalUrl":null,"permalink":"/tags/identity-provider/","section":"Tags","summary":"","title":"Identity Provider","type":"tags"},{"content":"","date":"10 February 2025","externalUrl":null,"permalink":"/tags/idp/","section":"Tags","summary":"","title":"Idp","type":"tags"},{"content":"","date":"10 February 2025","externalUrl":null,"permalink":"/tags/keycloak/","section":"Tags","summary":"","title":"Keycloak","type":"tags"},{"content":"Complete the following steps for each realm. There are 2 ways to do each step. One via cli and one via web ui. If you are going to use the cli options, ensure that at that step keycloak is not running.\nFirst, export the realm via cli or web gui. For the cli option use:\nkc export --realm (realm name) --file export.json If you want to export all realms use:\nkc export --file export.json Only cli can be used to import if you have exported all realms. Now transfer the exported file(s) and go to your second keycloak instance. You can try to import the realm using cli or web ui. It is easier to spot errors using cli import.\nFor cli import use:\nkc import --file export.json --verbose You will likely get errors while importing which will block the import proccess. You can try to edit the json file to get rid of errors.\nCommon fixes for following errors include:\nERROR: Script upload is disabled Remove or rewrite policies with \u0026quot;type\u0026quot;: \u0026quot;js\u0026quot;. If it is likely that these policies are not used (such as default policies) you can remove them. If you are removing a policy, search with its policy id to also remove roles depending on the policy.\njava.lang.RuntimeException: Error while importing policy [(policy name)] It is likely that the policy depends on a role or a policy that does not exists. Most likely you forgot to delete this policy when you were getting rid of previous error. Either search with the name of the role and delete it or re-add the role or rewrite the policy (keep in mind that you should not use \u0026quot;type\u0026quot;: \u0026quot;js\u0026quot;) when writing the policy.\norg.keycloak.authorization.policy.provider.util.PolicyValidationException: Role can\u0026#39;t be specified multiple times - (role name) It is likely that your export file includes a policy with duplicate role. Search with the role name and remove the dumplication in:\n{ \u0026#34;id\u0026#34;: \u0026#34;(policy id)\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;(policy name)\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;(policy description)\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;role\u0026#34;, \u0026#34;logic\u0026#34;: \u0026#34;POSITIVE\u0026#34;, \u0026#34;decisionStrategy\u0026#34;: \u0026#34;UNANIMOUS\u0026#34;, \u0026#34;config\u0026#34;: { \u0026#34;roles\u0026#34;: \u0026#34;[ {\\\u0026#34;id\\\u0026#34;:\\\u0026#34;(role name)\\\u0026#34;, \\\u0026#34;required\\\u0026#34; : false }, {\\\u0026#34;id\\\u0026#34;:\\\u0026#34;(role name)\\\u0026#34;, \\\u0026#34;required\\\u0026#34; : false } ]\u0026#34; } }, ","date":"10 February 2025","externalUrl":null,"permalink":"/posts/migrating_keycloak_instances/","section":"Posts","summary":"","title":"Migrating Keycloak Instances","type":"posts"},{"content":"","date":"10 February 2025","externalUrl":null,"permalink":"/tags/migration/","section":"Tags","summary":"","title":"Migration","type":"tags"},{"content":"","date":"10 February 2025","externalUrl":null,"permalink":"/tags/oauth2/","section":"Tags","summary":"","title":"Oauth2","type":"tags"},{"content":"","date":"10 February 2025","externalUrl":null,"permalink":"/tags/oidc/","section":"Tags","summary":"","title":"Oidc","type":"tags"},{"content":"","date":"10 February 2025","externalUrl":null,"permalink":"/tags/policy/","section":"Tags","summary":"","title":"Policy","type":"tags"},{"content":"","date":"10 February 2025","externalUrl":null,"permalink":"/tags/realm/","section":"Tags","summary":"","title":"Realm","type":"tags"},{"content":"","date":"10 February 2025","externalUrl":null,"permalink":"/tags/role/","section":"Tags","summary":"","title":"Role","type":"tags"},{"content":"We will be simulating an Kubernetes install with QEMU and Libvirt and use Harbor to cache container images.\nWe will have 4 VM\u0026rsquo;s for this job\nharbor: This machine will hold a Harbor instance to proxy our kubernetes image pull requests. control-panel: This machine will hold control panel for our kubernetes instance. worker-1 and worker-2: This machines will be our Kubernetes workers. First install libvirt, virt-install and qemu. The exact command might differ from system to system. I\u0026rsquo;m using Arch (btw.) and this command worked fine for me:\nsudo pacman -S libvirt virt-install qemu-full In order to use libvirtd, start libvirtd:\nsudo systemctl start libvirtd and add yourself to libvirt group:\nsudo usermod -a -G libvirt (your username) Now we can configure our virtual machines. First download your favourite distributions cloud image. I will be using Ubuntu Noble, you can select a different one if you want.\nDownload the cloud image to /var/lib/libvirt/images\nsudo curl -L -O --output-dir /var/lib/libvirt/images http://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img Create a new directory with following structure:\nkubernetes ├── control-panel ├── harbor ├── worker-1 └── worker-2 This directory will hold all of our machines and their configuration inside kubernetes directory create a kubernetes.xml to hold our network configuration. This xml file should look like:\n\u0026lt;network\u0026gt; \u0026lt;name\u0026gt;kubernetes\u0026lt;/name\u0026gt; \u0026lt;bridge name=\u0026#34;virbr1\u0026#34;/\u0026gt; \u0026lt;ip address=\u0026#34;192.168.2.1\u0026#34; netmask=\u0026#34;255.255.255.0\u0026#34;\u0026gt; \u0026lt;dhcp\u0026gt; \u0026lt;range start=\u0026#34;192.168.2.2\u0026#34; end=\u0026#34;192.168.2.254\u0026#34;/\u0026gt; \u0026lt;/dhcp\u0026gt; \u0026lt;/ip\u0026gt; \u0026lt;/network\u0026gt; Now use following to define and start the kubernetes network:\nsudo virsh net-define kubernetes.xml sudo virsh net-start kubernetes Also start the default network:\nsudo virsh net-start default Allow all users to access virbr1:\necho \u0026#34;allow virbr1\u0026#34; | sudo tee -a /etc/qemu/bridge.conf Now create VM images:\nqemu-img create -b /var/lib/libvirt/images/jammy-server-cloudimg-amd64.img -f qcow2 -F qcow2 harbor/harbor.img 20G qemu-img create -b /var/lib/libvirt/images/jammy-server-cloudimg-amd64.img -f qcow2 -F qcow2 control-panel/control-panel.img 20G qemu-img create -b /var/lib/libvirt/images/jammy-server-cloudimg-amd64.img -f qcow2 -F qcow2 worker-1/worker-1.img 20G qemu-img create -b /var/lib/libvirt/images/jammy-server-cloudimg-amd64.img -f qcow2 -F qcow2 worker-2/worker-2.img 20G We will use cloud-init to configure initial configuration. Create a file named harbor/meta-data with following contents:\ninstance-id: harbor local-hostname: harbor Create a file named harbor/user-data with following contents:\n#cloud-config users: - name: kubernetes-user ssh_authorized_keys: - (Your public ssh key) sudo: [\u0026#34;ALL=(ALL) NOPASSWD:ALL\u0026#34;] groups: sudo shell: /bin/bash For control-panel, worker-1 and worker-2 replicate the configuration in their respective directories. In the end you should have the following structure:\nkubernetes/ ├── control-panel │ ├── control-panel.img │ ├── meta-data │ └── user-data ├── harbor │ ├── harbor.img │ ├── meta-data │ └── user-data ├── kubernetes.xml ├── worker-1 │ ├── meta-data │ ├── user-data │ └── worker-1.img └── worker-2 ├── meta-data ├── user-data └── worker-2.img Now use virt-install to install the VM\u0026rsquo;s. Since harbor will need internet access we will also add the default network interface that comes with libvirt:\nvirt-install --name=harbor \\ --ram=2048 \\ --vcpus=2 \\ --import --disk path=harbor/harbor.img,format=qcow2 \\ --network bridge=virbr0,model=virtio \\ --network bridge=virbr1,model=virtio \\ --cloud-init user-data=harbor/user-data,meta-data=harbor/meta-data \\ --os-variant=ubuntu22.04 \\ --noautoconsole For other VM\u0026rsquo;s we don\u0026rsquo;t need the default bridge. For example can use following for control-panel:\nvirt-install --name=control-panel \\ --ram=2048 \\ --vcpus=2 \\ --import --disk path=control-panel/control-panel.img,format=qcow2 \\ --network bridge=virbr1,model=virtio \\ --cloud-init user-data=control-panel/user-data,meta-data=control-panel/meta-data \\ --os-variant=ubuntu22.04 \\ --noautoconsole sudo virsh net-dhcp-leases --network default sudo virsh net-dhcp-leases --network kubernetes to check your VM\u0026rsquo;s ip\u0026rsquo;s. You should be able to login to them with\nssh kubernetes-user@VMIP First login harbor and execute\nsudo ip a Note the mac address of the interface wihch does not have an IP. Create a /etc/netplan/10-private.yaml with following content:\nnetwork: version: 2 ethernets: enp2s0: match: macaddress: \u0026#34;NOTED MAC ADDRESS\u0026#34; dhcp4: true dhcp6: true set-name: \u0026#34;enp2s0\u0026#34; Then use sudo netplan apply. Second interface should get an IP address. Now we can install Harbor to our VM. Use the official instructions to install Docker and Docker-compose. In my case it is:\n# Add Docker\u0026#39;s official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: echo \\ \u0026#34;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \\ $(. /etc/os-release \u0026amp;\u0026amp; echo \u0026#34;$VERSION_CODENAME\u0026#34;) stable\u0026#34; | \\ sudo tee /etc/apt/sources.list.d/docker.list \u0026gt; /dev/null sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin Now go to the Harbor Releases Page and get the latest version.\ncurl -L -O https://github.com/goharbor/harbor/releases/download/v2.12.2/harbor-online-installer-v2.12.2.tgz Unarchive the installer\ntar xvzf harbor-online-installer-v2.12.2.tgz Copy and edit the harbor configuration:\ncd harbor cp harbor.yml.tmpl harbor.yml vim harbor.yml Now deploy harbor with:\nsudo ./install.sh Create a proxy cache as described in Harbor Documentation.\nNow we will use Kubekey to deploy our kubernetes cluster. If you are going to use harbor to use Kubekey, make sure that you can ssh into other VM\u0026rsquo;s from harbor.\nUse following to install Kubekey:\ncurl -sfL https://get-kk.kubesphere.io | sh - Then copy example manifest.yaml file and edit it to fit your setup as described in the official documentation.\nThen use\n./kk artifact export -m manifest.yaml -o kubesphere.tar.gz to generate the artifacts.\nNow generate Kubekey config with following command:\n./kk create config -f config.yml edit kubekey.yml to include your control-panel, worker-1 and worker-2 nodes.\nDon\u0026rsquo;t forget to include harbor\u0026rsquo;s ip to insecureRegisteries.\nYou can deploy your cluster:\n./kk create cluster -f config.yaml -a kubesphere.tar.gz --with-packages ","date":"21 January 2025","externalUrl":null,"permalink":"/posts/air_gapped_kubernetes_install_with_kubekey_and_harbor/","section":"Posts","summary":"","title":"Air Gapped Kubernetes Install with Kubekey and Harbor","type":"posts"},{"content":"","date":"21 January 2025","externalUrl":null,"permalink":"/tags/harbor/","section":"Tags","summary":"","title":"Harbor","type":"tags"},{"content":"","date":"21 January 2025","externalUrl":null,"permalink":"/tags/kubekey/","section":"Tags","summary":"","title":"Kubekey","type":"tags"},{"content":"","date":"21 January 2025","externalUrl":null,"permalink":"/tags/kubernetes/","section":"Tags","summary":"","title":"Kubernetes","type":"tags"},{"content":"","date":"21 January 2025","externalUrl":null,"permalink":"/tags/k3s/","section":"Tags","summary":"","title":"K3s","type":"tags"},{"content":"","date":"21 January 2025","externalUrl":null,"permalink":"/tags/libvirt/","section":"Tags","summary":"","title":"Libvirt","type":"tags"},{"content":"","date":"21 January 2025","externalUrl":null,"permalink":"/tags/qemu/","section":"Tags","summary":"","title":"Qemu","type":"tags"},{"content":"We will be simulating an Kubernetes install with QEMU and Libvirt and use Harbor to cache container images.\nWe will have 4 VM\u0026rsquo;s for this job\nharbor: This machine will hold a Harbor instance to proxy our kubernetes image pull requests. control-panel: This machine will hold control panel for our kubernetes instance. worker-1 and worker-2: This machines will be our Kubernetes workers. First install libvirt, virt-install and qemu. The exact command might differ from system to system. I\u0026rsquo;m using Arch (btw.) and this command worked fine for me:\nsudo pacman -S libvirt virt-install qemu-full In order to use libvirtd, start libvirtd:\nsudo systemctl start libvirtd and add yourself to libvirt group:\nsudo usermod -a -G libvirt (your username) Now we can configure our virtual machines. First download your favourite distributions cloud image. I will be using Ubuntu Noble, you can select a different one if you want.\nDownload the cloud image to /var/lib/libvirt/images\nsudo curl -L -O --output-dir /var/lib/libvirt/images http://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img Create a new directory with following structure:\nkubernetes ├── control-panel ├── harbor ├── worker-1 └── worker-2 This directory will hold all of our machines and their configuration inside kubernetes directory create a kubernetes.xml to hold our network configuration. This xml file should look like:\n\u0026lt;network\u0026gt; \u0026lt;name\u0026gt;kubernetes\u0026lt;/name\u0026gt; \u0026lt;bridge name=\u0026#34;virbr1\u0026#34;/\u0026gt; \u0026lt;forward mode=\u0026#34;nat\u0026#34;/\u0026gt; \u0026lt;ip address=\u0026#34;192.168.2.1\u0026#34; netmask=\u0026#34;255.255.255.0\u0026#34;\u0026gt; \u0026lt;dhcp\u0026gt; \u0026lt;range start=\u0026#34;192.168.2.2\u0026#34; end=\u0026#34;192.168.2.254\u0026#34;/\u0026gt; \u0026lt;/dhcp\u0026gt; \u0026lt;/ip\u0026gt; \u0026lt;/network\u0026gt; Now use following to define and start the kubernetes network:\nsudo virsh net-define kubernetes.xml sudo virsh net-start kubernetes Allow all users to access virbr1:\necho \u0026#34;allow virbr1\u0026#34; | sudo tee -a /etc/qemu/bridge.conf Now create VM images:\nqemu-img create -b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img -f qcow2 -F qcow2 harbor/harbor.img 20G qemu-img create -b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img -f qcow2 -F qcow2 control-panel/control-panel.img 20G qemu-img create -b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img -f qcow2 -F qcow2 worker-1/worker-1.img 20G qemu-img create -b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img -f qcow2 -F qcow2 worker-2/worker-2.img 20G We will use cloud-init to configure initial configuration. Create a file named harbor/meta-data with following contents:\ninstance-id: harbor local-hostname: harbor Create a file named harbor/user-data with following contents:\n#cloud-config users: - name: kubernetes-user ssh_authorized_keys: - (Your public ssh key) sudo: [\u0026#34;ALL=(ALL) NOPASSWD:ALL\u0026#34;] groups: sudo shell: /bin/bash For control-panel, worker-1 and worker-2 replicate the configuration in their respective directories. In the end you should have the following structure:\nkubernetes/ ├── control-panel │ ├── control-panel.img │ ├── meta-data │ └── user-data ├── harbor │ ├── harbor.img │ ├── meta-data │ └── user-data ├── kubernetes.xml ├── worker-1 │ ├── meta-data │ ├── user-data │ └── worker-1.img └── worker-2 ├── meta-data ├── user-data └── worker-2.img Now use virt-install to install the VM\u0026rsquo;s:\nvirt-install --name=harbor \\ --ram=2048 \\ --vcpus=2 \\ --import --disk path=harbor/harbor.img,format=qcow2 \\ --network bridge=virbr1,model=virtio \\ --cloud-init user-data=harbor/user-data,meta-data=harbor/meta-data \\ --os-variant=ubuntu24.04 \\ --noautoconsole Other VM\u0026rsquo;s can be started similarly. Use\nsudo virsh net-dhcp-leases --network kubernetes to check your VM\u0026rsquo;s ip\u0026rsquo;s. You should be able to login to them with\nssh kubernetes-user@VMIP Now we can proceed to setup kubernetes. I will use K3S for simplicity. First, ssh into control-panel and install k3s to control with:\ncurl -sfL https://get.k3s.io | sh - Save the node token from sudo cat /var/lib/rancher/k3s/server/node-token.\nThen ssh into worker-1 and worker-2 and use\ncurl -sfL https://get.k3s.io | K3S_URL=https://CONTROL-PANEL-IP:6443 K3S_TOKEN=NODE-TOKEN sh - to join them into the kubernetes cluster. Now we will install Harbor to our harbor VM. SSH into harbor and use the official instructions to install Docker and Docker-compose. In my case it is:\n# Add Docker\u0026#39;s official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: echo \\ \u0026#34;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \\ $(. /etc/os-release \u0026amp;\u0026amp; echo \u0026#34;$VERSION_CODENAME\u0026#34;) stable\u0026#34; | \\ sudo tee /etc/apt/sources.list.d/docker.list \u0026gt; /dev/null sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin Now go to the Harbor Releases Page and get the latest version.\ncurl -L -O https://github.com/goharbor/harbor/releases/download/v2.12.2/harbor-online-installer-v2.12.2.tgz Unarchive the installer\ntar xvzf harbor-online-installer-v2.12.2.tgz Copy and edit the harbor configuration:\ncd harbor cp harbor.yml.tmpl harbor.yml vim harbor.yml Now deploy harbor with:\nsudo ./install.sh Create a proxy cache as described in Harbor Documentation.\nIn every node of your k3s installation, create a /etc/rancher/k3s/registries.yaml file as described in K3S Documentation with following contents:\nrules: - name: \u0026#39;Redirect DockerIO requests\u0026#39; matches: - \u0026#39;^docker.io\u0026#39; replace: \u0026#39;HARBOR-IP/PROXY_CACHE_PROJECT_NAME\u0026#39; checkUpstream: true Restart k3s in your nodes. You should be able to use kubernetes with Harbor container image proxy.\n","date":"21 January 2025","externalUrl":null,"permalink":"/posts/using_harbor_as_container_image_cache_for_kubernetes/","section":"Posts","summary":"","title":"Using Harbor as Container Image Cache for Kubernetes","type":"posts"},{"content":"","date":"17 December 2024","externalUrl":null,"permalink":"/tags/background/","section":"Tags","summary":"","title":"Background","type":"tags"},{"content":" Generating Background Images with ImageMagick # Requirements # ImageMagick Image Pattern You can draw it yourself or download one from a website like Subtle Patterns Generating image # For this purpose imagemagick\u0026rsquo;s tile option fits our needs. Use the following command:\nmagick -size {RESOLUTION} tile:{YOUR PATTERN} {OUTPUT FILE} Resolution should be width x height (for example 1920x1080) and pattern should be a valid image file. It is that simple.\n","date":"17 December 2024","externalUrl":null,"permalink":"/posts/generating_background_images_with_image_magick/","section":"Posts","summary":"","title":"Generating Background Images with ImageMagick","type":"posts"},{"content":"","date":"17 December 2024","externalUrl":null,"permalink":"/tags/imagemagick/","section":"Tags","summary":"","title":"Imagemagick","type":"tags"},{"content":"","date":"17 December 2024","externalUrl":null,"permalink":"/tags/subtle-patterns/","section":"Tags","summary":"","title":"Subtle-Patterns","type":"tags"},{"content":"","date":"4 December 2024","externalUrl":null,"permalink":"/tags/embeded/","section":"Tags","summary":"","title":"Embeded","type":"tags"},{"content":"","date":"4 December 2024","externalUrl":null,"permalink":"/tags/esp32/","section":"Tags","summary":"","title":"Esp32","type":"tags"},{"content":"","date":"4 December 2024","externalUrl":null,"permalink":"/tags/microros/","section":"Tags","summary":"","title":"Microros","type":"tags"},{"content":"","date":"4 December 2024","externalUrl":null,"permalink":"/tags/ros2/","section":"Tags","summary":"","title":"Ros2","type":"tags"},{"content":" Using Barebones ESP-ID with Microros # Requirements # ROS2 Docker (for running micro ROS on host) Setting Up IDF # In order to setup IDF you first have to install its prerequisets. Use following command for Ubuntu and Debian (taken from official IDF documentation):\nsudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 Next create a directory to install IDF (prefeberably accessible by you unpriviledged user) and clone the ESP-IDF repository:\nexport IDF_PATH=\u0026#34;$HOME/.local/opt/esp-idf\u0026#34; mkdir -p \u0026#34;$IDF_PATH\u0026#34; git clone -b v5.3.1 --recursive https://github.com/espressif/esp-idf.git \u0026#34;$IDF_PATH\u0026#34; Next cd into the directory and install the toolchain with\ncd \u0026#34;$IDF_PATH\u0026#34; ./install.sh esp32 # Or other toolchains like esp32s2 etc Next add \u0026ldquo;$IDF_PATH\u0026rdquo; to your shell config for persistancy (in this cas I am using bash, adopt it to your shell):\necho \u0026#34;export IDF_PATH=\\\u0026#34;$IDF_PATH\\\u0026#34;\u0026#34; \u0026gt;\u0026gt; \u0026#34;$HOME/.bashrc\u0026#34; . \u0026#34;$HOME/.bashrc\u0026#34; Setting Up Microros ESP-IDF Component # Note: Most of the steps are taken from the microros esp-idf componenet README and modified to adopt this tutorial.\nFirst clone the repository:\ngit clone --depth 1 --recurse-submodules https://github.com/micro-ROS/micro_ros_espidf_component.git -b \u0026#34;$ROS_DISTRO\u0026#34; Then install the dependencies:\ncd micro_ros_espidf_component . $IDF_PATH/export.sh pip3 install catkin_pkg lark-parser colcon-common-extensions Compiling Example Node # Now you can build the example node for your ESP32 and flash your firmware.\nIn project directory execute:\n. $IDF_PATH/export.sh . /opt/ros/$ROS_DISTRO/setup.bash cd examples/int32_publisher idf.py set-target esp32 # Set target board [esp32|esp32s2|esp32s3|esp32c3] idf.py menuconfig # Set your micro-ROS configuration and WiFi credentials under micro-ROS Settings idf.py build Now that you have built your firmware you can start flashing it But before that check if you have access to serial ports for communicating with ESP32. If not, you might need to add yourself to dialout group with:\nsudo usermod -a -G dialout $(whoami) or chown the serial device with:\nsudo chown $(whoami) /dev/ttyUSB* After ensuring that you have access to serial ports, flash the firmware:\nidf.py flash You can also monitor the serial port with idf.py monitor.\nTesting your configuration # You can run microros agent with:\ndocker run -it --rm --net=host microros/micro-ros-agent:$ROS_DISTRO udp4 --port 8888 -v6 Also Keep in Mind # If you have not configured an IP Address for ESP32, you need to have a DHCP server. Look at dnsmasq for a lightweight dhcp server.\n","date":"4 December 2024","externalUrl":null,"permalink":"/posts/barebones_idf_with_microros/","section":"Posts","summary":"","title":"Using Barebones ESP-ID with Microros","type":"posts"},{"content":"","date":"20 November 2024","externalUrl":null,"permalink":"/tags/platformio/","section":"Tags","summary":"","title":"Platformio","type":"tags"},{"content":" Introduction # Requirements # ROS2 Platform IO Core Docker (for running micro ROS on host) Setting up a platformio project # Firstly create a platform io project for your board with\npio project init -b esp32dev -d (PROJECT DIRECTORY) if you have a different board, you can check available boards with pio boards.\nAdding microROS # Firstly specifly your ROS distribution by adding following lines to platformio.ini located at the root of your project:\nboard_microros_distro = humble Change the value humble for other distributions. Then add following lines to install micro-ROS as a dependency for platformio:\nlib_deps = https://github.com/micro-ROS/micro_ros_platformio Writing firmware for ESP32 # Copy following lines to src/main.cpp\n#include \u0026lt;Arduino.h\u0026gt; #include \u0026lt;micro_ros_platformio.h\u0026gt; #include \u0026lt;rcl/rcl.h\u0026gt; #include \u0026lt;rclc/rclc.h\u0026gt; #include \u0026lt;rclc/executor.h\u0026gt; #include \u0026lt;std_msgs/msg/int32.h\u0026gt; #if !defined(MICRO_ROS_TRANSPORT_ARDUINO_SERIAL) #error This example is only avaliable for Arduino framework with serial transport. #endif rcl_publisher_t publisher; std_msgs__msg__Int32 msg; rclc_executor_t executor; rclc_support_t support; rcl_allocator_t allocator; rcl_node_t node; rcl_timer_t timer; #define RCCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){error_loop();}} #define RCSOFTCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){}} // Error handle loop void error_loop() { while(1) { delay(100); } } void timer_callback(rcl_timer_t * timer, int64_t last_call_time) { RCLC_UNUSED(last_call_time); if (timer != NULL) { RCSOFTCHECK(rcl_publish(\u0026amp;publisher, \u0026amp;msg, NULL)); msg.data++; } } void setup() { // Configure serial transport Serial.begin(115200); set_microros_serial_transports(Serial); delay(2000); allocator = rcl_get_default_allocator(); //create init_options RCCHECK(rclc_support_init(\u0026amp;support, 0, NULL, \u0026amp;allocator)); // create node RCCHECK(rclc_node_init_default(\u0026amp;node, \u0026#34;micro_ros_platformio_node\u0026#34;, \u0026#34;\u0026#34;, \u0026amp;support)); // create publisher RCCHECK(rclc_publisher_init_default( \u0026amp;publisher, \u0026amp;node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32), \u0026#34;micro_ros_platformio_node_publisher\u0026#34;)); // create timer, const unsigned int timer_timeout = 1000; RCCHECK(rclc_timer_init_default( \u0026amp;timer, \u0026amp;support, RCL_MS_TO_NS(timer_timeout), timer_callback)); // create executor RCCHECK(rclc_executor_init(\u0026amp;executor, \u0026amp;support.context, 1, \u0026amp;allocator)); RCCHECK(rclc_executor_add_timer(\u0026amp;executor, \u0026amp;timer)); msg.data = 0; } void loop() { delay(100); RCSOFTCHECK(rclc_executor_spin_some(\u0026amp;executor, RCL_MS_TO_NS(100))); } (gracefully taken from micro_ros_platformio project)\nFlashing firmware # Thankfully it is simple to flash firmware with platformio. Run\npio run -t upload If you cannot connect to device it might be related to permissions of the serial port. Give yourself permissions for the serial port. If you can not find the serial, try\npio device list Testing your configuration # Run following to check if our microros setup is running correctly:\ndocker run -it --rm -v /dev:/dev -v /dev/shm:/dev/shm --privileged --net=host microros/micro-ros-agent:$ROS_DISTRO serial --dev /dev/ttyUSB0 -v6 If everything has been setup up correctly you should be able to see your node sending messages. You can check if your node is connected by\nros2 node list If you see an entry /micro_ros_platformio_node then everything is working.\n","date":"20 November 2024","externalUrl":null,"permalink":"/posts/using_ros_and_platform_io_on_esp32s/","section":"Posts","summary":"","title":"Using ROS2 and Platform IO on ESP32","type":"posts"},{"content":" Introduction # Yosys and Verilator are two powerful free and open source tools that will help us automate verification, testing, simulation, and synthesis in our circuit designs. In this post, we will be implementing a half adder.\nHalf Adder # Truth table for a half adders is as follows:\nA B Sum Carry 0 0 0 0 0 1 1 0 1 0 1 0 1 1 0 1 Observe that\n$$ \\text{Sum} = A \\oplus B \\quad \\text{Carry} = AB $$thus in order to have a half adder we will have the following verilog module\n/* modules/halfadder.v */ module halfadder( input\tA, input\tB, output\tSum, output\tCarry); assign Sum = A ^ B; assign Carry = A \u0026amp;\u0026amp; B; endmodule Now that we have a half adder, we need a simple test case to use with Verilator. Following test case we be enough for our neeeds\n/* simulations/sim_halfadder.cpp */ #include \u0026#34;Vhalfadder.h\u0026#34; #include \u0026#34;verilated_vcd_c.h\u0026#34; int main(int argc, char** argv) { Verilated::commandArgs(argc, argv); Verilated::traceEverOn(true); std::unique_ptr\u0026lt;Vhalfadder\u0026gt; halfadder = std::make_unique\u0026lt;Vhalfadder\u0026gt;(); std::unique_ptr\u0026lt;VerilatedVcdC\u0026gt; m_trace = std::make_unique\u0026lt;VerilatedVcdC\u0026gt;(); halfadder-\u0026gt;trace(m_trace.get(), 5); m_trace-\u0026gt;open(\u0026#34;halfadder_waveform.vcd\u0026#34;); for(int i = 0; i \u0026lt; 1 \u0026lt;\u0026lt; 2; i++) { halfadder-\u0026gt;A = (0b10 \u0026amp; i) \u0026gt;\u0026gt; 1; halfadder-\u0026gt;B = (0b1 \u0026amp; i) \u0026gt;\u0026gt; 0; halfadder-\u0026gt;eval(); m_trace-\u0026gt;dump(i); } m_trace-\u0026gt;close(); return 0; } we will use cmake to verilate out verilog modules an example CMakeLists.txt can be as following\n# CMakeLists.txt cmake_minimum_required(VERSION 3.25) project(halfadder) find_package(verilator HINTS $ENV{VERILATOR_ROOT}) add_executable(Vhalfadder simulations/sim_halfadder.cpp) verilate(Vhalfadder SOURCES modules/halfadder.v TRACE) After completing everything, we can use cmake -S . -B build and cmake --build build/ to build our verilator output. Use cd build; ./Vhalfadder; cd .. to generate waveform, then use gtkwave build/halfadder_waveform.vcd to open generated waveform.\nNow that we have tested our verilog code, we can move onto the synthesis step. For this purpose we will use yosys. Yosys can read instructions from a file with -s flag. An example of yosys instruction file can be as\n# synthesize.ys read_verilog modules/halfadder.v synth write_json build/halfadder_synthesized.json write_verilog build/halfadder_synthesized.v Then use yosys -s synthesize.ys to synthesize our verilog code. You can also generate a .svg vector image by using netlistsvg build/halfadder_synthesized.json -o build/halfadder_synthesized.svg.\n","date":"28 March 2024","externalUrl":null,"permalink":"/posts/going_from_zero_to_hero_with_yosys_and_verilator/","section":"Posts","summary":"","title":"Going from Zero to Hero with Yosys and Verilator","type":"posts"},{"content":"","date":"28 March 2024","externalUrl":null,"permalink":"/tags/logic-design/","section":"Tags","summary":"","title":"Logic Design","type":"tags"},{"content":"","date":"28 March 2024","externalUrl":null,"permalink":"/tags/verilator/","section":"Tags","summary":"","title":"Verilator","type":"tags"},{"content":"","date":"28 March 2024","externalUrl":null,"permalink":"/tags/verilog/","section":"Tags","summary":"","title":"Verilog","type":"tags"},{"content":"","date":"28 March 2024","externalUrl":null,"permalink":"/tags/yosys/","section":"Tags","summary":"","title":"Yosys","type":"tags"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]