<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Bakri Anouar]]></title><description><![CDATA[Computer science trick shots !]]></description><link>https://blog.abakri.xyz/</link><image><url>https://blog.abakri.xyz/favicon.png</url><title>Bakri Anouar</title><link>https://blog.abakri.xyz/</link></image><generator>Ghost 5.75</generator><lastBuildDate>Fri, 01 May 2026 05:24:52 GMT</lastBuildDate><atom:link href="https://blog.abakri.xyz/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[OpenClaw on Kubernetes : A Step-by-Step Guide]]></title><description><![CDATA[<p>So I wanted to have my own OpenClaw running at home, on my own server, not depending on some cloud service. OpneClaw it&apos;s basically an multi-agent infrastructure that you can talk to via Telegram, browser, whatever you want. Here is how I did it on my Rancher/RKE2</p>]]></description><link>https://blog.abakri.xyz/openclaw-on-kubernetes/</link><guid isPermaLink="false">69dbd0a303391b000190ab9a</guid><dc:creator><![CDATA[BAKRI Anouar]]></dc:creator><pubDate>Sun, 12 Apr 2026 20:51:50 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1712980502482-9dd8f7e0cc63?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fGNsYXd8ZW58MHx8fHwxNzc2MDI3MDUzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1712980502482-9dd8f7e0cc63?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fGNsYXd8ZW58MHx8fHwxNzc2MDI3MDUzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="OpenClaw on Kubernetes : A Step-by-Step Guide"><p>So I wanted to have my own OpenClaw running at home, on my own server, not depending on some cloud service. OpneClaw it&apos;s basically an multi-agent infrastructure that you can talk to via Telegram, browser, whatever you want. Here is how I did it on my Rancher/RKE2 cluster.</p>
<hr>
<h2 id="my-setup">My Setup</h2>
<p>Before starting, here is what I have:</p>
<ul>
<li>Rancher v2.13.3</li>
<li>RKE2 cluster (4 nodes)</li>
<li>kubectl + kustomize</li>
<li>A storage class called <code>local-path</code></li>
<li>An Anthropic API key (you get it from console.anthropic.com)</li>
</ul>
<hr>
<h2 id="how-openclaw-is-meant-to-be-deployed">How OpenClaw is meant to be deployed</h2>
<p>The official OpenClaw repo (<code>github.com/openclaw/openclaw</code>) has a <code>scripts/k8s/</code> folder with everything you need:</p>
<pre><code>scripts/k8s/
&#x251C;&#x2500;&#x2500; deploy.sh           # creates namespace + secret, deploys via kustomize
&#x251C;&#x2500;&#x2500; create-kind.sh      # local Kind cluster for testing
&#x2514;&#x2500;&#x2500; manifests/
    &#x251C;&#x2500;&#x2500; kustomization.yaml
    &#x251C;&#x2500;&#x2500; configmap.yaml  # openclaw.json + AGENTS.md
    &#x251C;&#x2500;&#x2500; deployment.yaml
    &#x251C;&#x2500;&#x2500; pvc.yaml
    &#x2514;&#x2500;&#x2500; service.yaml
</code></pre>
<p>You can run <code>./scripts/k8s/deploy.sh</code> and it handles everything. I took these manifests, adapted them for my Rancher cluster (different storage class, my config, my AGENTS.md), and kept them in my own git repo.</p>
<p>The manifests use <strong>Kustomize</strong> instead of just running <code>kubectl apply -f</code> on each file separately. The reason is simple: with Kustomize you have one <code>kustomization.yaml</code> that lists all your files, so <code>kubectl apply -k ./folder/</code> applies everything in one shot in the right order. It also makes it easy to manage overlays later (dev vs prod configs for example) without duplicating files. It is built into kubectl so no extra tool to install.</p>
<hr>
<h2 id="step-1-%E2%80%94-create-the-namespace">Step 1 &#x2014; Create the Namespace</h2>
<pre><code class="language-bash">kubectl create namespace openclaw
</code></pre>
<hr>
<h2 id="step-2-%E2%80%94-create-the-secrets">Step 2 &#x2014; Create the Secrets</h2>
<p>You need two things in the secret: your Anthropic API key, and a gateway token (used to authenticate the web UI).</p>
<p><strong>Important:</strong> never put secrets in git. Always create them directly from command line.</p>
<pre><code class="language-bash"># Generate a random gateway token
GATEWAY_TOKEN=$(openssl rand -hex 24)

kubectl create secret generic openclaw-secrets \
  --from-literal=ANTHROPIC_API_KEY=&quot;sk-ant-XXXXXXX&quot; \
  --from-literal=OPENCLAW_GATEWAY_TOKEN=&quot;$GATEWAY_TOKEN&quot; \
  -n openclaw

# Save the token &#x2014; you need it to access the UI
echo &quot;Your token: $GATEWAY_TOKEN&quot;
</code></pre>
<p>Key names must be exact &#x2014; <code>ANTHROPIC_API_KEY</code> not <code>anthropicApiKey</code>.</p>
<hr>
<h2 id="step-3-%E2%80%94-create-the-pvc">Step 3 &#x2014; Create the PVC</h2>
<p>Use your cluster&apos;s default storage class. I use Longhorn (replicated, survives node failures). Do not use <code>local-path</code> &#x2014; if the PVC gets deleted, data is gone permanently.</p>
<pre><code class="language-yaml"># pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: openclaw
  namespace: openclaw
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 10Gi
</code></pre>
<hr>
<h2 id="step-4-%E2%80%94-create-the-config-files">Step 4 &#x2014; Create the Config Files</h2>
<p>OpenClaw uses a ConfigMap for two things: <code>openclaw.json</code> (the gateway config) and <code>AGENTS.md</code> (instructions for the agent).</p>
<p>Create <code>configmap.yaml</code>:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: ConfigMap
metadata:
  name: openclaw-config
  labels:
    app: openclaw
data:
  openclaw.json: |
    {
      &quot;agents&quot;: {
        &quot;defaults&quot;: {
          &quot;model&quot;: {
            &quot;primary&quot;: &quot;anthropic/claude-haiku-4-5-20251001&quot;
          }
        }
      },
      &quot;gateway&quot;: {
        &quot;mode&quot;: &quot;local&quot;,
        &quot;bind&quot;: &quot;loopback&quot;,
        &quot;port&quot;: 18789,
        &quot;auth&quot;: {
          &quot;mode&quot;: &quot;token&quot;
        },
        &quot;controlUi&quot;: {
          &quot;enabled&quot;: true,
          &quot;allowedOrigins&quot;: [&quot;http://localhost:18789&quot;]
        }
      }
    }
  AGENTS.md: |
    # OpenClaw Assistant
    You are a helpful assistant running on a personal Kubernetes cluster.
    Be direct and concise.
</code></pre>
<h3 id="llm-choice">LLM Choice</h3>
<p>This is a personal assistant running 24/7. Every message costs tokens. Here is the rough pricing (early 2026):</p>
<table>
<thead>
<tr>
<th>Model</th>
<th>Input (per 1M tokens)</th>
<th>Output (per 1M tokens)</th>
</tr>
</thead>
<tbody>
<tr>
<td>claude-haiku-4-5</td>
<td>~$0.80</td>
<td>~$4</td>
</tr>
<tr>
<td>claude-sonnet-4</td>
<td>~$3</td>
<td>~$15</td>
</tr>
<tr>
<td>claude-opus-4</td>
<td>~$15</td>
<td>~$75</td>
</tr>
</tbody>
</table>
<p>Haiku is ~5x cheaper than Sonnet. For daily tasks it is more than enough. Switch to Sonnet per-session when you need deeper reasoning.</p>
<h3 id="about-allowedorigins">About allowedOrigins</h3>
<p>The gateway needs to know which browser origins can connect. Since I access via <code>kubectl port-forward</code> on <code>localhost:18789</code>, I set exactly that. If you access from a different host or over HTTPS/Tailscale, update this accordingly.</p>
<p>Also useful to know:</p>
<ul>
<li>Pass the token as <code>#token=...</code> in the URL fragment (not <code>?token=</code>) &#x2014; fragments are not sent to the server, more secure</li>
<li>The <code>gatewayUrl</code> is saved in localStorage after first load</li>
<li>For HTTPS/TLS use <code>wss://</code> not <code>ws://</code></li>
</ul>
<hr>
<h2 id="step-5-%E2%80%94-create-the-deployment-and-service">Step 5 &#x2014; Create the Deployment and Service</h2>
<h3 id="deploymentyaml">deployment.yaml</h3>
<p>A few things worth knowing about this file:</p>
<ul>
<li><strong>Init container</strong> (<code>init-config</code>): runs before the main container. It copies <code>openclaw.json</code> and <code>AGENTS.md</code> from the ConfigMap into the PVC. This happens on every restart &#x2014; so the ConfigMap is always the source of truth for config.</li>
<li><strong>Image</strong>: pinned to <code>2026.4.11</code>, not <code>latest</code>. Always pin to a release.</li>
<li><strong><code>readOnlyRootFilesystem: true</code></strong>: the container filesystem is read-only for security. That&apos;s why we mount <code>/tmp</code> separately as an emptyDir &#x2014; npm needs a writable cache somewhere.</li>
<li><strong><code>npm_config_cache=/tmp/.npm-cache</code></strong>: fixes npm permission errors inside the pod.</li>
<li>No CPU/memory limits &#x2014; let it use what it needs.</li>
</ul>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: openclaw
  labels:
    app: openclaw
spec:
  replicas: 1
  selector:
    matchLabels:
      app: openclaw
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: openclaw
    spec:
      automountServiceAccountToken: false
      securityContext:
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      initContainers:
        - name: init-config
          image: busybox:1.37
          command:
            - sh
            - -c
            - |
              cp /config/openclaw.json /home/node/.openclaw/openclaw.json
              mkdir -p /home/node/.openclaw/workspace
              cp /config/AGENTS.md /home/node/.openclaw/workspace/AGENTS.md
          securityContext:
            runAsUser: 1000
            runAsGroup: 1000
          resources:
            requests:
              memory: 32Mi
              cpu: 50m
            limits:
              memory: 64Mi
              cpu: 100m
          volumeMounts:
            - name: openclaw-home
              mountPath: /home/node/.openclaw
            - name: config
              mountPath: /config
      containers:
        - name: gateway
          image: ghcr.io/openclaw/openclaw:2026.4.11
          command: [node, /app/dist/index.js, gateway, run]
          ports:
            - containerPort: 18789
          env:
            - name: HOME
              value: /home/node
            - name: OPENCLAW_CONFIG_DIR
              value: /home/node/.openclaw
            - name: NODE_ENV
              value: production
            - name: npm_config_cache
              value: /tmp/.npm-cache
            - name: OPENCLAW_GATEWAY_TOKEN
              valueFrom:
                secretKeyRef:
                  name: openclaw-secrets
                  key: OPENCLAW_GATEWAY_TOKEN
            - name: ANTHROPIC_API_KEY
              valueFrom:
                secretKeyRef:
                  name: openclaw-secrets
                  key: ANTHROPIC_API_KEY
                  optional: true
          resources:
            requests:
              memory: 512Mi
              cpu: 250m
          livenessProbe:
            exec:
              command: [node, -e, &quot;require(&apos;http&apos;).get(&apos;http://127.0.0.1:18789/healthz&apos;, r =&gt; process.exit(r.statusCode &lt; 400 ? 0 : 1)).on(&apos;error&apos;, () =&gt; process.exit(1))&quot;]
            initialDelaySeconds: 60
            periodSeconds: 30
          readinessProbe:
            exec:
              command: [node, -e, &quot;require(&apos;http&apos;).get(&apos;http://127.0.0.1:18789/readyz&apos;, r =&gt; process.exit(r.statusCode &lt; 400 ? 0 : 1)).on(&apos;error&apos;, () =&gt; process.exit(1))&quot;]
            initialDelaySeconds: 15
            periodSeconds: 10
          volumeMounts:
            - name: openclaw-home
              mountPath: /home/node/.openclaw
            - name: tmp-volume
              mountPath: /tmp
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            runAsGroup: 1000
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: [ALL]
      volumes:
        - name: openclaw-home
          persistentVolumeClaim:
            claimName: openclaw
        - name: config
          configMap:
            name: openclaw-config
        - name: tmp-volume
          emptyDir: {}
</code></pre>
<h3 id="serviceyaml">service.yaml</h3>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: openclaw
  labels:
    app: openclaw
spec:
  type: ClusterIP
  selector:
    app: openclaw
  ports:
    - port: 18789
      targetPort: 18789
</code></pre>
<h3 id="kustomizationyaml">kustomization.yaml</h3>
<pre><code class="language-yaml">apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: openclaw
resources:
  - pvc.yaml
  - configmap.yaml
  - deployment.yaml
  - service.yaml
</code></pre>
<h3 id="apply-everything">Apply everything</h3>
<pre><code class="language-bash">kubectl apply -k ./kustomize/
</code></pre>
<hr>
<h2 id="step-6-%E2%80%94-access-the-ui">Step 6 &#x2014; Access the UI</h2>
<pre><code class="language-bash">kubectl port-forward svc/openclaw 18789:18789 -n openclaw
</code></pre>
<p>Then open: <code>http://localhost:18789#token=YOUR_TOKEN</code></p>
<p>Get your token anytime:</p>
<p><strong>Linux/Mac:</strong></p>
<pre><code class="language-bash">kubectl get secret openclaw-secrets -n openclaw \
  -o jsonpath=&apos;{.data.OPENCLAW_GATEWAY_TOKEN}&apos; | base64 -d
</code></pre>
<hr>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>It took me a few hours, mostly because I started with the wrong Helm chart. Once I switched to the official Kustomize manifests it was much cleaner. Now I have my own AI agent running 24/7 at home, connected to Telegram, with persistent memory. Pretty cool for automating things and having a real assistant that remembers context between conversations.</p>
<p>If you have questions feel free to reach out.</p>
]]></content:encoded></item><item><title><![CDATA[Deploying a Self-Hosted Ghost Blog on Kubernetes]]></title><description><![CDATA[<p>In this post, I&apos;ll walk you through the process of deploying a self-hosted Ghost blog on a Kubernetes cluster. </p><p>There are a couple of key reasons why I chose to do this. First and foremost, I wanted to save money by not paying $11 per month for the</p>]]></description><link>https://blog.abakri.xyz/deploying-a-self-hosted-ghost-blog-on-kubernetes/</link><guid isPermaLink="false">658e10aa4c62d700014ed472</guid><dc:creator><![CDATA[BAKRI Anouar]]></dc:creator><pubDate>Fri, 29 Dec 2023 01:09:26 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1603516863860-7d5c93a83fe8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGdob3N0fGVufDB8fHx8MTcwMzgxMTk5NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1603516863860-7d5c93a83fe8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGdob3N0fGVufDB8fHx8MTcwMzgxMTk5NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Deploying a Self-Hosted Ghost Blog on Kubernetes"><p>In this post, I&apos;ll walk you through the process of deploying a self-hosted Ghost blog on a Kubernetes cluster. </p><p>There are a couple of key reasons why I chose to do this. First and foremost, I wanted to save money by not paying $11 per month for the Ghost Pro subscription, especially since I wasn&apos;t utilizing all the premium features. Additionally, I recently set up a home server, and I was keen to maximize its resources by running various services. </p><p>In this tutorial I will use the following docker image to botstrap the blog.</p><p><a href="https://github.com/docker-library/ghost?tab=readme-ov-file&amp;ref=blog.abakri.xyz">https://github.com/docker-library/ghost?tab=readme-ov-file</a></p><p></p><p><strong><em>Storage Setup</em></strong></p><p><br>The first thing that comes to my mind on this journey is that I want my blog content data to be stored outside the container. This way, data is not linked to container lifecycle.</p><p>For that, I created a <em>StorageClass</em>, <em>PersistentVolume</em>, <em>PersistentVolumeClaim</em></p><ul><li>StorageClass</li></ul><pre><code class="language-yaml">apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
  namespace: internet-apps
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer</code></pre><ul><li>PersistentVolume</li></ul><pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolume
metadata:
  name: ghost-pv
  namespace: internet-apps
  labels:
    type: local
spec:
  storageClassName: local-storage
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: &quot;/data/ghost&quot;</code></pre><p>The <code>hostPath</code> volumes need to be accessible on all nodes in your cluster. <code>b</code>ecause it ties to the node a pod is on, so the path needs to be there and reachable on any node where the pod might run.</p><ul><li><em>PersistentVolumeClaim</em></li></ul><pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ghost-pvc
  namespace: internet-apps
spec:
  storageClassName: local-storage
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  volumeName: ghost-pv</code></pre><p><strong>Deployment</strong></p><p>The Ghost Docker image allows managing configuration outside of the container in a MySQL database. For this, I created a config map to manage MySQL connection values:</p><ul><li>ConfigMap mysql-config</li></ul><pre><code class="language-yaml">apiVersion: v1
data:
  MYSQL_HOST: mysql-service.internet-apps.svc.cluster.local
  MYSQL_USER: xxxxx
kind: ConfigMap
metadata:
  name: mysql-config
  namespace: internet-apps</code></pre><ul><li>ConfigMap wp-db-secrets</li></ul><pre><code class="language-yaml">apiVersion: v1
data:
  MYSQL_ROOT_PASSWORD: xxxxx
kind: Secret
metadata:
  name: wp-db-secrets
  namespace: internet-apps</code></pre><p>We also need to specify the URL, which is the public URL of your Ghost blog.</p><ul><li>Url</li></ul><p>In my case : <a href="https://blog.abakri.xyz/">https://blog.v2.abakri.xyz</a></p><ul><li>Deployment Config file</li></ul><pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: ghost
  namespace: internet-apps
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ghost
  template:
    metadata:
      labels:
        app: ghost
    spec:
      containers:
      - name: ghost
        image: ghost:5-alpine
        env:
          - name: database__client
            value: &quot;mysql&quot;
          - name: database__connection__host
            valueFrom:
              configMapKeyRef:
                name: mysql-config
                key: MYSQL_HOST
          - name: database__connection__user
            valueFrom:
              configMapKeyRef:
                name: mysql-config
                key: MYSQL_USER
          - name: database__connection__password
            valueFrom:
              secretKeyRef:
                name: wp-db-secrets
                key: MYSQL_ROOT_PASSWORD
          - name: database__connection__database
            value: &quot;xxx&quot;
          - name: url
            value: &quot;https://blog.v2.abakri.xyz&quot;
        ports:
        - containerPort: 2368
        volumeMounts:
        - name: ghost-storage
          mountPath: &quot;/var/lib/ghost/content&quot;
      volumes:
      - name: ghost-storage
        persistentVolumeClaim:
          claimName: ghost-pvc</code></pre><p><strong>Service</strong></p><ul><li>Cluster IP Service<br>targetPort need to be 2368 and not 80 ;)</li></ul><pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: ghost-service
  namespace: internet-apps
spec:
  selector:
    app: ghost
  ports:
    - protocol: TCP
      port: 80
      targetPort: 2368</code></pre><ul><li>Ingress</li></ul><p>You must have an ingress controller (Nginx in my case), and cert-manager needs to be deployed with letsencrypt-prod configured.</p><pre><code class="language-yaml">apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ghost-ingress
  namespace: internet-apps
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  rules:
    - host: blog.abakri.xyz
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: ghost-service
                port:
                  number: 80
  tls:
    - hosts:
        - blog.abakri.xyz
      secretName: ghost-service-tls</code></pre><ul><li>Domain Configuration(Google Domains)</li></ul><p>Bingo !</p>]]></content:encoded></item><item><title><![CDATA[Building a Custom ESXi 7 Image with Net-Community Drivers: A Step-by-Step Guide]]></title><description><![CDATA[<h2 id></h2><p>When attempting to install Esxi 7 on a PC/server equipped with the latest hardware available in the market, you may encounter the following problem:</p>
<!--kg-card-begin: html-->
<table data-line="5" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(212, 212, 212); font-family: -apple-system, BlinkMacSystemFont, " segoe wpc", "segoe ui", system-ui, ubuntu, "droid sans", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: font-variant-caps: font-weight: 400; letter-spacing: orphans: 2; text-align: start; text-transform: none; white-space: widows: word-spacing: 0px; -webkit-text-stroke-width: text-decoration-thickness: initial; text-decoration-style: text-decoration-color: initial;"><thead data-line="5" class="code-line" dir="auto" style="position: relative;"><tr data-line="5" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(255, 255, 255, 0.69); padding: 5px 10px; border-top-color: rgba(255, 255, 255, 0.69); border-right-color: rgba(255, 255, 255, 0.69); border-left-color: rgba(255, 255, 255, 0.69);">No network adapters were detected. Either no network adapters are physically connected to the system, or a suitable driver could not be located. A third</th></tr></thead></table>]]></description><link>https://blog.abakri.xyz/building-a-custom-esxi-7-image-with-net-community-drivers-a-step-by-step-guide/</link><guid isPermaLink="false">658e0db14c62d700014ed40a</guid><dc:creator><![CDATA[BAKRI Anouar]]></dc:creator><pubDate>Sun, 26 Feb 2023 11:35:09 GMT</pubDate><media:content url="https://blog.abakri.xyz/content/images/2023/12/esxi-install-error-no-network-adapters.png" medium="image"/><content:encoded><![CDATA[<h2 id></h2><img src="https://blog.abakri.xyz/content/images/2023/12/esxi-install-error-no-network-adapters.png" alt="Building a Custom ESXi 7 Image with Net-Community Drivers: A Step-by-Step Guide"><p>When attempting to install Esxi 7 on a PC/server equipped with the latest hardware available in the market, you may encounter the following problem:</p>
<!--kg-card-begin: html-->
<table data-line="5" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(212, 212, 212); font-family: -apple-system, BlinkMacSystemFont, " segoe wpc", "segoe ui", system-ui, ubuntu, "droid sans", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: font-variant-caps: font-weight: 400; letter-spacing: orphans: 2; text-align: start; text-transform: none; white-space: widows: word-spacing: 0px; -webkit-text-stroke-width: text-decoration-thickness: initial; text-decoration-style: text-decoration-color: initial;"><thead data-line="5" class="code-line" dir="auto" style="position: relative;"><tr data-line="5" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(255, 255, 255, 0.69); padding: 5px 10px; border-top-color: rgba(255, 255, 255, 0.69); border-right-color: rgba(255, 255, 255, 0.69); border-left-color: rgba(255, 255, 255, 0.69);">No network adapters were detected. Either no network adapters are physically connected to the system, or a suitable driver could not be located. A third party driver may be required. Ensure that there is at least one network adapter physically connected to the system before attempting installation. If the problem persists, consult the VMware Knowledge Base.</th></tr></thead></table>
<!--kg-card-end: html-->
<p>I faced it with the Mini Forum Um690.</p><p>To resolve this issue, we need to build a custom ESXi iso image that will include drivers that support the undetected network chip.</p><p>Steps :</p><ul><li>You need to download the VMware vSphere Hypervisor (ESXi) Offline Bundle(VMware-ESXi-7.0U3g-20328353-depot.zip) from Vmware customer connect(<a href="https://customerconnect.vmware.com/?ref=blog.abakri.xyz">https://customerconnect.vmware.com</a>)</li><li>You need also the network driver from Vmware fings, <a href="https://flings.vmware.com/community-networking-driver-for-esxi?ref=blog.abakri.xyz">https://flings.vmware.com/community-networking-driver-for-esxi</a></li><li>Install Python 3.7.9 or any 3.7.x<br><em>3.7.0 didn&apos;t work for me, better to choose 3.7.9 or a newer version</em></li><li>Upgrading pip</li></ul><pre><code class="language-powershell">python -m pip install --upgrade pip</code></pre><ul><li>Installing required Python packages</li></ul><pre><code class="language-powershell">pip install six psutil lxml pyopenssl</code></pre><ul><li>Enable remote signed script</li></ul><pre><code class="language-powershell">Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser</code></pre><ul><li>Install PowerCli<br>If you want to use a different version of PowerCLI, ensure its compatibility with your Esxi version by checking on <a href="https://interopmatrix.vmware.com/Interoperability?ref=blog.abakri.xyz" rel="nofollow">https://interopmatrix.vmware.com/Interoperability</a>.</li></ul><pre><code class="language-powershell">Install-Module -Name VMware.PowerCLI -RequiredVersion 13.0.0.20829139</code></pre><ul><li>Import the ImageBuilder module to create the custom iso</li></ul><pre><code class="language-powershell">Import-Module VMware.ImageBuilder</code></pre><ul><li>Add the downloaded bundle depot to the PowerCli session</li></ul><pre><code class="language-powershell">Add-EsxSoftwareDepot .\VMware-ESXi-7.0U3g-20328353-depot.zip</code></pre><ul><li>Verify that the depot is loaded correctly</li></ul><pre><code class="language-powershell">Get-EsxImageProfile</code></pre><ul><li>Now, we create the custom image from the already loaded depot profile</li></ul><pre><code class="language-powershell">New-EsxImageProfile -CloneProfile ESXi-7.0U3g-20328353-standard -Name Custom-Esxi7 -Vendor Vmware</code></pre><ul><li>Here we add the network community driver(net-community) to the PowerCli session</li></ul><pre><code class="language-powershell">Add-EsxSoftwareDepot .\Net-Community-Driver_1.2.7.0-1vmw.700.1.0.15843807_19480755.zip</code></pre><ul><li>Now, we add the net-community driver to our custom image profile</li></ul><pre><code class="language-powershell">Add-EsxSoftwarePackage -ImageProfile Custom-Esxi7 -SoftwarePackage net-community</code></pre><ul><li>Because we have a community package inside our image profile, we need to switch the acceptance level to CommunitySupported instead of PartnerSupported</li></ul><pre><code class="language-powershell">Set-EsxImageProfile -AcceptanceLevel CommunitySupported &#x2013;ImageProfile Custom-Esxi7</code></pre><ul><li>Here we create the iso file from the profile</li></ul><pre><code class="language-powershell">Export-EsxImageProfile -ImageProfile Custom-Esxi7 -ExportToIso -FilePath C:\Users\User\Desktop\esxi-image\Custom-Esxi7.iso</code></pre><ul><li>Bingo ! Now you can use this iso to install Esxi 7</li></ul>]]></content:encoded></item><item><title><![CDATA[Configuring the maven settings.xml in the Bitbucket pipeline]]></title><description><![CDATA[<p>This week I was setting up a minimal Continuous Integration solution based on Java/Maven running on Bitbucket pipelines. I was looking for the cleanest way to integrate the maven settings.xml into the pipeline, I found some solutions in the internet but they were not as clean as I</p>]]></description><link>https://blog.abakri.xyz/configuring-settings-xml-in-bitbucket-pipeline/</link><guid isPermaLink="false">658e0db14c62d700014ed409</guid><dc:creator><![CDATA[BAKRI Anouar]]></dc:creator><pubDate>Mon, 20 Feb 2023 18:00:22 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1507823690283-48b0929e727b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fHBpcGVsaW5lfGVufDB8fHx8MTY3NjkxNjE1Nw&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1507823690283-48b0929e727b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fHBpcGVsaW5lfGVufDB8fHx8MTY3NjkxNjE1Nw&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Configuring the maven settings.xml in the Bitbucket pipeline"><p>This week I was setting up a minimal Continuous Integration solution based on Java/Maven running on Bitbucket pipelines. I was looking for the cleanest way to integrate the maven settings.xml into the pipeline, I found some solutions in the internet but they were not as clean as I want. In this article I share with you a simple and clean solution to do it.</p><h3 id="environment-variables">Environment variables</h3><p>The first thing to do is to define a couple of variables in Bitbucket. My advice is to define them at the workspace level rather than the repository level.</p><ul><li><em>MVN_SERVER1_USERNAME</em> : Will contain the username of the server with the id SERVER1</li><li><em>MVN_SERVER1_PASSWORD </em>: Will contain the password of the server with the id SERVER1</li><li><em>SETTINGS_XML</em> : Will contain the settings.xml payload, BUT with placeholder for the server credentials(Yes, bitbucket Accept nested variables, good news !!!)</li><li>You can define as many servers credentials as you need...<br><br>The server element in the settings.xml look like this : </li></ul><pre><code class="language-xml">&lt;server&gt;
    &lt;id&gt;SERVER1&lt;/id&gt;
    &lt;username&gt;${MVN_SERVER1_USERNAME}&lt;/username&gt;
    &lt;password&gt;${MVN_SERVER1_PASSWORD}&lt;/password&gt;
&lt;/server&gt;</code></pre><p>Here are the environment variables in Bitbucket</p><figure class="kg-card kg-image-card"><img src="https://blog.abakri.xyz/content/images/2023/12/image.png" class="kg-image" alt="Configuring the maven settings.xml in the Bitbucket pipeline" loading="lazy" width="888" height="231" srcset="https://blog.abakri.xyz/content/images/size/w600/2023/12/image.png 600w, https://blog.abakri.xyz/content/images/2023/12/image.png 888w" sizes="(min-width: 720px) 720px"></figure><h3 id="pipeline">Pipeline</h3><p>The pipeline is straightforward and easy to understand : </p><pre><code class="language-yaml">image: maven:3.6.3-jdk-11

pipelines:
  default:      
      - step:
          name: Build and Test
          script:
            - echo $SETTINGS_XML &gt; settings.xml
            - mvn -s settings.xml clean install</code></pre><p>I hope this can help you in your Bitbucket pipelines journey :)</p><p>Salam<br></p>]]></content:encoded></item><item><title><![CDATA[Toast system using Typescript, Vue 3 and bootstrap 5]]></title><description><![CDATA[<p>Hi, I wanted to create a toast error system for my web application where whenever an error is thrown a toast is created with the error body and appended to a div element. the final result looks like this : </p><figure class="kg-card kg-image-card"><img src="https://blog.abakri.xyz/content/images/2023/12/image-1.png" class="kg-image" alt loading="lazy" width="375" height="449"></figure><h2 id="whats-in-the-stack">What&apos;s in the stack</h2><ul><li>Vue 3/ Composition API</li><li>Bootstrap</li></ul>]]></description><link>https://blog.abakri.xyz/toast-system-using-vue-3-bootstrap-5/</link><guid isPermaLink="false">658e0db14c62d700014ed406</guid><dc:creator><![CDATA[BAKRI Anouar]]></dc:creator><pubDate>Sun, 29 May 2022 14:15:24 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1505491589101-5c7976eeb482?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDE4fHx0b2FzdHxlbnwwfHx8fDE2NTM4MzM5OTg&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1505491589101-5c7976eeb482?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDE4fHx0b2FzdHxlbnwwfHx8fDE2NTM4MzM5OTg&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Toast system using Typescript, Vue 3 and bootstrap 5"><p>Hi, I wanted to create a toast error system for my web application where whenever an error is thrown a toast is created with the error body and appended to a div element. the final result looks like this : </p><figure class="kg-card kg-image-card"><img src="https://blog.abakri.xyz/content/images/2023/12/image-1.png" class="kg-image" alt="Toast system using Typescript, Vue 3 and bootstrap 5" loading="lazy" width="375" height="449"></figure><h2 id="whats-in-the-stack">What&apos;s in the stack</h2><ul><li>Vue 3/ Composition API</li><li>Bootstrap 5.1</li><li>Typescript 4.6</li></ul><h2 id="deep-dive">Deep dive</h2><p>Firstly we create a Vue component called <strong><em>Toast.vue</em></strong> that encapsulates the UI and the Algorithm that processes toast show up.</p><h3 id="the-setup-script-block-is-as-follows">The setup script block is as follows :</h3><pre><code class="language-typescript">&lt;script setup lang=&quot;ts&quot;&gt;
import { onUpdated } from &quot;vue&quot;;
import { Toast } from &quot;bootstrap&quot;;

const props = defineProps({
  errors: { type: Array, default: () =&gt; [] },
});

onUpdated(() =&gt; {
  const hiddenToasts = props.errors.filter((obj) =&gt; {
    return obj.show != true;
  });
  hiddenToasts.forEach(function (error) {
    var errorToast = document.getElementById(error.id);
    var toast = new Toast(errorToast);
    toast.show();
    error.show = true;
    errorToast.addEventListener(&quot;hidden.bs.toast&quot;, function () {
      const indexOfObject = props.errors.findIndex((item) =&gt; {
        return item.id === error.id;
      });
      if (indexOfObject !== -1) {
        props.errors.splice(indexOfObject, 1);
      }
    });
  });
});
&lt;/script&gt;</code></pre><blockquote><em>errors </em>array will host toast&apos;s states, it can be anything. In my case, it&apos;s an object with an id, msg, and a boolean show state</blockquote><blockquote><a href="https://vuejs.org/api/composition-api-lifecycle.html?ref=blog.abakri.xyz#onmounted">onUpdated()</a> is a Vue lifecycle hooks triggered whenever the component has updated its DOM tree due to a reactive state change, in our case the reactive state is the errors array.</blockquote><p>1.	Firstly we filter the errors that are not shown yet, then for each error we trigger <em>toast.show() and we add a listener on </em>hidden.bs.toast<br>2.	Whatever an event <em>hidden.bs.toast</em> is triggered we remove the error object from the array so that we have a nice and clean DOM</p><p></p><h3 id="the-script-block-is-as-follows">The  script block is as follows :</h3><pre><code class="language-typescript">&lt;script lang=&quot;ts&quot;&gt;
const TOASTS_MAX = 5;
export function push(array: Array, data): Array {
  if (array.length == TOASTS_MAX) {
    array.shift();
    array.push(data);
  } else {
    array.push(data);
  }
}
&lt;/script&gt;</code></pre><ul><li><em>TOASTS_MAX defines the max number of toasts to show up at the same time</em></li><li><em>the push method is a simple way to implement a </em>circular array. Whenever the array contains <em>TOASTS_MAX elements, adding a new one will delete the element at the position 0 and push the new element at the end, in this way we are sure to have a maximum of TOASTS_MAX elements </em> </li><li>the push method needs to be exported so that other components can use it</li></ul><h3 id="the-template-block-is-as-follows">The template block is as follows :</h3><pre><code class="language-HTML">&lt;template&gt;
  &lt;div ref=&quot;container&quot; class=&quot;position-fixed bottom-0 end-0 p-3&quot; style=&quot;z-index: 11&quot;&gt;
    &lt;div v-for=&quot;item in errors&quot; v-bind:id=&quot;item.id&quot; class=&quot;toast fade opacity-75 bg-danger&quot; role=&quot;alert&quot; aria-live=&quot;assertive&quot; aria-atomic=&quot;true&quot; data-bs-delay=&quot;15000&quot;&gt;
      &lt;div class=&quot;toast-header bg-danger&quot;&gt;
        &lt;strong class=&quot;me-auto text-white&quot;&gt;Error&lt;/strong&gt;
        &lt;button type=&quot;button&quot; class=&quot;btn-close&quot; data-bs-dismiss=&quot;toast&quot; aria-label=&quot;Close&quot;&gt;&lt;/button&gt;
      &lt;/div&gt;
      &lt;div class=&quot;toast-body text-white error-body&quot;&gt;{{ item.msg }}&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;</code></pre><ul><li><em>&lt;div ref=&quot;container&quot;...&gt; fixed in the bottom right host the toasts</em></li><li><em>v-for</em> directive loop on errors array and render it whenever a change happens.</li><li><em>v-bind:id directive</em> let us define an id for each toast, the id needs to be unique!</li><li><em>data-bs-delay</em> let us define the timeout before auto-hiding the toast</li></ul><h3 id="how-to-use-it">How to use it?</h3><pre><code class="language-HTML">&lt;script setup lang=&quot;ts&quot;&gt;
import { ref, reactive } from &apos;vue&apos;;
import toast from &apos;./Toast.vue&apos;;
import { push } from &apos;./Toast.vue&apos;;

const state = reactive({ errors: [], count : 0 });

function pushError(id: int) {
  push(state.errors, { id: id, msg: &apos;Error message &apos; + id });
}
&lt;/script&gt;

&lt;template&gt;
  &lt;toast :errors=&quot;state.errors&quot;&gt;&lt;/toast&gt;
  &lt;button type=&quot;button&quot; @click=&quot;pushError(&apos;toast&apos; + state.count++)&quot;&gt;
    Error trigger: {{ state.count }}
  &lt;/button&gt;
&lt;/template&gt;</code></pre><ul><li>state hold the reactive <em>errors </em>array and the count that forms the error&apos;s id</li><li>Whenever the button is clicked an error object is pushed to the errors array and because the errors array is attached to the Toast component using <em>:errors=&quot;state.errors&quot;</em> whenever we change the source array the target array is updated </li></ul><p>Here is the <a href="https://stackblitz.com/edit/vitejs-vite-mcjgkl?ref=blog.abakri.xyz">Demo</a>!</p>]]></content:encoded></item></channel></rss>