<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>Rico&apos;s Blog</title><description>Build it. Break it. Automate it.</description><link>https://blog.endtoend.work</link><item><title>The Hidden Danger of select on a Shared ioredis Connnection</title><link>https://blog.endtoend.work/blog/markdown/2026-03-16-lessons-learn-from-ioredis-select-mistake</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2026-03-16-lessons-learn-from-ioredis-select-mistake</guid><description>This post documents what went wrong in using ioredis</description><pubDate>Sun, 15 Mar 2026 03:37:00 GMT</pubDate><content:encoded>&lt;p&gt;A subtle but painful bug recently cost me a significant amount of debugging time.
This post documents what went wrong, what the root cause was, and how to avoid
the same trap.&lt;/p&gt;
&lt;h2&gt;The Setup&lt;/h2&gt;
&lt;p&gt;I have a Node.js application using a single persistent ioredis client shared
across API endpoints. Two of those endpoints read from different Redis databases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/endpoint0&lt;/code&gt; reads from db 0 using a straightforward &lt;code&gt;GET&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const res = await client.get(someKey)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/endpoint2&lt;/code&gt; reads from db 2 using a pipeline with &lt;code&gt;SELECT&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const [res1, res2] = await client.multi().select(2).hget(hash, key).exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/endpoint0&lt;/code&gt; was added well after &lt;code&gt;/endpoint2&lt;/code&gt; was already in production. Shortly
after it was introduced, an intermittent failure started appearing — calls to
&lt;code&gt;/endpoint0&lt;/code&gt; would begin returning incorrect results some time after a fresh
deployment, and a redeployment would temporarily resolve it.&lt;/p&gt;
&lt;h2&gt;The Investigation&lt;/h2&gt;
&lt;p&gt;The bug manifested across all environments — dev, UAT, and production — which
ruled out environment-specific configuration issues. I checked dependency
declaration order, environment variable handling, and the deployment pipeline
itself. None of those investigations turned up anything.&lt;/p&gt;
&lt;p&gt;The breakthrough came when I inspected the output of &lt;code&gt;CLIENT LIST&lt;/code&gt; directly on
the Redis server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;id=501766576 addr=192.168.1.57:61819 fd=36 name= age=701 idle=623 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=hget

id=501692280 addr=192.168.1.57:24319 fd=10 name= age=3619 idle=4 flags=N db=2 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;db=2&lt;/code&gt; field on the second connection immediately stood out. A connection
that should have been operating on db 0 was sitting on db 2 — which pointed
directly at the &lt;code&gt;select&lt;/code&gt; call in &lt;code&gt;/endpoint2&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;The Root Cause&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;SELECT&lt;/code&gt; in Redis — and by extension &lt;code&gt;client.select()&lt;/code&gt; or &lt;code&gt;client.multi().select()&lt;/code&gt;
in ioredis — is a &lt;strong&gt;stateful, connection-level operation&lt;/strong&gt;. It does not scope to
a single command or pipeline; it changes the active database for that connection
and the change persists until another &lt;code&gt;SELECT&lt;/code&gt; is issued.&lt;/p&gt;
&lt;p&gt;When a shared connection is used, any call to &lt;code&gt;SELECT&lt;/code&gt; affects every subsequent
command on that connection, regardless of which endpoint or function issued them.
In this case, once &lt;code&gt;/endpoint2&lt;/code&gt; ran and switched the connection to db 2, any
subsequent call from &lt;code&gt;/endpoint0&lt;/code&gt; was unknowingly querying db 2 instead of db 0. Refer to this diagram for a better understanding of the problem:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;ioredis-select.png&quot; alt=&quot;mermaid chart explaining the bug&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The Fix&lt;/h2&gt;
&lt;p&gt;In this specific case, the simplest resolution was to migrate the data for
&lt;code&gt;/endpoint2&lt;/code&gt; into db 0, eliminating the need for &lt;code&gt;SELECT&lt;/code&gt; entirely:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const res = await client.hget(hash, key)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The more robust and general solution — especially in applications where multiple
databases are genuinely required — is to &lt;strong&gt;create a dedicated ioredis client per
database&lt;/strong&gt;. Calling &lt;code&gt;SELECT&lt;/code&gt; at runtime on a shared connection is a latent bug
waiting to surface, and it will do so in ways that are difficult to reproduce and
trace.&lt;/p&gt;
&lt;h2&gt;Takeaway&lt;/h2&gt;
&lt;p&gt;If you are using ioredis with multiple Redis databases in a shared-connection
setup, avoid &lt;code&gt;SELECT&lt;/code&gt; at runtime. Provision one client per database instead, and
make the database assignment explicit at initialization. It is a small upfront
cost that eliminates an entire class of hard-to-debug, timing-dependent failures.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>How I upgraded my home lab from Nginx to Caddy</title><link>https://blog.endtoend.work/blog/markdown/tech-stack-update</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/tech-stack-update</guid><description>The tech stack update path for my home lab</description><pubDate>Sun, 15 Mar 2026 03:37:00 GMT</pubDate><content:encoded>&lt;p&gt;I have a registered domain with Cloudflare — the blog you&apos;re reading right now is just one of the sites running on it. I also host a password manager, &lt;a href=&quot;https://github.com/dani-garcia/vaultwarden&quot;&gt;Vaultwarden&lt;/a&gt;, behind a WAF.&lt;/p&gt;
&lt;p&gt;When I first set up my home lab, I used Nginx as the middleman between Cloudflare and my applications, with Let&apos;s Encrypt handling SSL. For extra protection, I ran a Redis server to dynamically manage a whitelist of allowed IPs — only requests from those IPs could reach protected sites like Vaultwarden or the Nextcloud instance I share with my family. I built a SvelteKit app (publicly accessible) to manage the whitelist, with authentication handled by &lt;a href=&quot;https://pocketbase.io/&quot;&gt;Pocketbase&lt;/a&gt;. The actual WAF check happened inside Nginx via the Redis Lua module.&lt;/p&gt;
&lt;p&gt;It worked, but it had a few pain points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every single request to a protected site triggered an IP whitelist lookup&lt;a href=&quot;https://gist.github.com/itbdw/bc6c03f754cc30f66b824f379f3da30f&quot;&gt;^footnote&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Caching helped with performance, but introduced a lag — a newly whitelisted IP could take up to 10 seconds to be recognized&lt;/li&gt;
&lt;li&gt;Wildcard SSL certificate from Let&apos;s Encrypt needs manual renewal every 3 months&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&apos;s the architecture diagram of that original setup:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.endtoend.work/_astro/prev-tech-stack-diagram.Sw4o9M-o_Z16pj3t.webp&quot; alt=&quot;Previous tech stack&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then I came across Caddy. I read a post from someone who&apos;d migrated their entire stack from Nginx to Caddy, got curious, and started experimenting a few weeks ago. I followed this Youtube tutorial&lt;a href=&quot;https://youtu.be/ZOtUco5EwoI?si=fnEfVF7-jAzyJPKb&quot;&gt;^tutorial&lt;/a&gt; and was honestly blown away — automatic SSL certificate renewal alone made it worth the switch.&lt;/p&gt;
&lt;p&gt;I also found a much cleaner approach to IP whitelisting. Instead of checking IPs inside my home lab on every request, I moved that logic to Cloudflare, managing the whitelist via their [list items API]:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PUT https://api.cloudflare.com/client/v4/accounts/{account_id}/rules/lists/{list_id}/items
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A local SQLite database (used by the better-auth library) keeps track of the listed IPs on my end. The big win here: disallowed requests to the protected apps never even reach my home router.&lt;/p&gt;
&lt;p&gt;Here&apos;s what the new stack looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.endtoend.work/_astro/curr-tech-stack-diagram.BpsBFKY6_Z2r3fDG.webp&quot; alt=&quot;Current tech stack&quot;&gt;&lt;/p&gt;
&lt;p&gt;The original Excalidraw file for both diagrams is &lt;a href=&quot;https://excalidraw.com/#json=K-FRAeYe-29ZdRjGDqMlo,tHJsNPj1HoockKvVnpBsgw&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;References&lt;/h1&gt;</content:encoded><h:img src="/_astro/curr-tech-stack-diagram.BpsBFKY6.png"/><enclosure url="/_astro/curr-tech-stack-diagram.BpsBFKY6.png"/></item><item><title>How to wakeup/shutdown a PBS server from proxmox</title><link>https://blog.endtoend.work/blog/markdown/2026-03-11-proxmox-backup-using-wakeonlan</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2026-03-11-proxmox-backup-using-wakeonlan</guid><description>Proxmox backup using wakeonlan and remote shutdown</description><pubDate>Wed, 11 Mar 2026 22:21:00 GMT</pubDate><content:encoded>&lt;h1&gt;Intro&lt;/h1&gt;
&lt;p&gt;I have the following simple backup setup for my Proxmox server. Since backup schedule is weekly, it would make a lot of sense to bring the backup server up only when it&apos;s needed. I decided to make my own script after seeing one on &lt;a href=&quot;%5Bhttps://forum.proxmox.com/threads/auto-poweroff-proxmox-backup-server-after-backup-finishes-hook-script-option.105493/%5D(https://forum.proxmox.com/threads/auto-poweroff-proxmox-backup-server-after-backup-finishes-hook-script-option.105493/)&quot;&gt;^reference&lt;/a&gt; Proxmox forum.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;proxmox.png&quot; alt=&quot;Proxmox&quot;&gt;&lt;/p&gt;
&lt;h1&gt;Setup&lt;/h1&gt;
&lt;p&gt;Here are the steps&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;configure the PBS server&apos;s BIOS so Wake-on-lan feature is enabled&lt;/li&gt;
&lt;li&gt;install &lt;code&gt;wakeonlan&lt;/code&gt; program on the Proxmox server if it&apos;s not already installed&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apt install wakeonlan
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;set up password-less login from Proxmox server&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-keygen -t ed25519
ssh-copy-id root@$PBS
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;create /usr/local/bin/vzdump-hook-script as follow and make it executable&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
IP=&amp;#x3C;LAN_IP_OF_PBS&gt;
MAC=&amp;#x3C;MAC_ADDRESS_OF_PBS&gt;
# wait for at least  $COUNT pings
COUNT=80

# check if wakeonlan is installed
which -s wakeonlan
if [ $? -ne 0 ]; then
    echo &quot;Please install wakeonlan first&quot;
    exit 1
fi
if [ &quot;$1&quot; == &quot;job-init&quot; ]; then
    if ! ping -c 1 -W 1 &quot;$IP&quot; &gt;/dev/null 2&gt;&amp;#x26;1; then
        /usr/bin/wakeonlan $MAC   #(mac Address of PBS Server)
        ping $IP -c $COUNT
    fi
fi

if [ &quot;$1&quot; == &quot;job-end&quot; ]; then
    ssh root@$IP &quot;/usr/sbin/poweroff &amp;#x3C; /dev/null &amp;#x26;&quot;
fi

exit 0
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;update /etc/pve/jobs.cfg to add this line&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;script /usr/local/bin/vzdump-hook-script
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Reference&lt;/h1&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>How to handle file uploads using actix-web</title><link>https://blog.endtoend.work/blog/markdown/2022-08-09-how-to-handle-file-uploads-using-actix-web</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2022-08-09-how-to-handle-file-uploads-using-actix-web</guid><description>File uploads with actix-web</description><pubDate>Tue, 09 Aug 2022 23:21:59 GMT</pubDate><content:encoded>&lt;p&gt;In this tutorial I&apos;ll demonstrate how to handle upload with additional data fields using one of the most popular Rust web frameworks - &lt;a href=&quot;https://github.com/actix/actix-web&quot;&gt;actix-web&lt;/a&gt;, which has become my go-to web framework when developing in Rust.&lt;/p&gt;
&lt;p&gt;We&apos;ll start by creating a binary Rust package&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo new doc-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then under the project root, run&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo add actix-web actix-multipart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With your favorite editor, open &lt;code&gt;src/main.rs&lt;/code&gt; and copy/paste the following code&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use serde::Serialize;
use actix_multipart::Multipart;
use futures_util::TryStreamExt as _;

use actix_web::{ post, App, Error as ActixError, HttpResponse, HttpServer };

#[derive(Serialize)]
struct Stats {
    lines: usize,
    #[serde(skip_serializing_if = &quot;Option::is_none&quot;)]
    word_count: Option&amp;#x3C;usize&gt;
}

#[post(&quot;/upload_stats&quot;)]
async fn upload_stats(
    mut payload: Multipart,
) -&gt; Result&amp;#x3C;HttpResponse, ActixError&gt; {
    let mut file_data = Vec::&amp;#x3C;u8&gt;::new();
    let mut layout: Option&amp;#x3C;String&gt; = Some(&quot;simple&quot;.to_owned());
    while let Some(mut field) = payload.try_next().await? {
        let content_disposition = field.content_disposition();
        let field_name = content_disposition.get_name().unwrap();
        match field_name {
            &quot;file&quot; =&gt; {
                while let Some(chunk) = field.try_next().await? {
                    file_data.extend_from_slice(&amp;#x26;chunk);
                }
            }
            &quot;layout&quot; =&gt; {
                let bytes = field.try_next().await?;
                layout = String::from_utf8(bytes.unwrap().to_vec()).ok();
            }
            _ =&gt; {}
        }
    }
    let file_content = std::str::from_utf8(&amp;#x26;file_data)?;
    let mut i = 0;
    let mut word_count=0;
    for line in file_content.lines() {
        word_count+=line.chars().count();
        i += 1;
    }
    let word_count_res = if layout.unwrap() == String::from(&quot;advanced&quot;) {
        Some(word_count)
    } else {
        None
    };
    Ok(HttpResponse::Ok().json(Stats {
        lines: i,
        word_count: word_count_res
    }))
}

#[actix_web::main]
async fn main() -&gt; std::io::Result&amp;#x3C;()&gt; {
     HttpServer::new(move || {
        App::new()
            .service(upload_stats)
    })
    .bind((&quot;127.0.0.1&quot;, 8080))?
    .run()
    .await
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This simple web application has only one single POST endpoint that will accept&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;a field named &lt;code&gt;file&lt;/code&gt; that points to a file in client&apos;s file system&lt;/li&gt;
&lt;li&gt;an optional field named &lt;code&gt;layout&lt;/code&gt;, its value is defaulted to &lt;code&gt;simple&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By default the output will be the line count of the file being uploaded, but a &lt;code&gt;characters&lt;/code&gt; result that represents the number of characters in the file will be added if &lt;code&gt;layout&lt;/code&gt; is set to &lt;code&gt;advanced&lt;/code&gt;. So for example,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl http://localhost:8080/upload_stats -X POST -F &apos;file=@Cargo.toml&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;returns something like&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;lines&quot;: 13}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl http://localhost:8080/upload_stats -X POST -F &apos;file=@Cargo.toml&apos; -F &apos;layout=advanced&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;might produce something like&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;lines&quot;: 13, &quot;characters&quot;: 311}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;p.s. here&apos;s the full content of &lt;code&gt;Cargo.toml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[package]
name = &quot;doc-demo&quot;
version = &quot;0.1.0&quot;
edition = &quot;2021&quot;

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-multipart = &quot;0.4.0&quot;
actix-web = &quot;4.1.0&quot;
futures-util = &quot;0.3.21&quot;
serde = { version = &quot;1.0.136&quot;, features = [&quot;derive&quot;] }
serde_json = &quot;1.0.81&quot;
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Add error code(s) to Hapijs output</title><link>https://blog.endtoend.work/blog/markdown/2019-06-08-add-error-code-s-to-hapijs-validation-output</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2019-06-08-add-error-code-s-to-hapijs-validation-output</guid><description>Add error code(s) to Hapijs output</description><pubDate>Sat, 08 Jun 2019 15:51:01 GMT</pubDate><content:encoded>&lt;p&gt;Joi validation is powerful and easy to work with, however it&apos;s not always obvious or easy to add stuff like error code(s) to the hapijs response. This post will show you a way (or two) to deal with that problem.&lt;/p&gt;
&lt;h2&gt;Step 1, assign error codes to each validation error&lt;/h2&gt;
&lt;p&gt;Quick example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;server.route({
  method: &apos;POST&apos;,
  path: &apos;/person&apos;,
  options: {
    validate: {
      payload: {
        firstName: Joi.string()
          .min(5)
          .max(10)
          .required()
          .error(errors =&gt; {
            errors.forEach(err =&gt; {
              switch (err.type) {
                case &apos;any.empty&apos;:
                case &apos;any.required&apos;:
                  err.message = &apos;Firstname should not be empty!&apos;
                  err.context = {
                    errorCode: 111
                  }
                  break
                case &apos;string.min&apos;:
                  err.message = `Firstname should have at least ${
                    err.context.limit
                  } characters!`
                  err.context = {
                    errorCode: 121
                  }
                  break
                case &apos;string.max&apos;:
                  err.message = `Firstname should have at most ${
                    err.context.limit
                  } characters!`
                  err.context = {
                    errorCode: 131
                  }
                  break
                default:
                  break
              }
            })
            return errors
          })
      }
    }
  },
  handler: async request =&gt; {
    // todo: handle saving the paylod
    console.log(&apos;to save&apos;, request.payload)
    return { result: &apos;ok&apos; }
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 2, customize failAction when creating Hapi server&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const Boom = require(&apos;@hapi/boom&apos;)
const server = Hapi.Server({
  // ...
  routes: {
    validate: {
      failAction: (request, h, err) =&gt; {
        const firstError = err.details[0]
        if (firstError.context.errorCode !== undefined) {
          throw Boom.badRequest(err.message, {
            errorCode: firstError.context.errorCode
          })
        } else {
          throw Boom.badRequest(err.message)
        }
      }
    }
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 3, customize response&lt;/h2&gt;
&lt;p&gt;The reason this step is needed is because Hapi would strip out the injected &lt;code&gt;errorCode&lt;/code&gt; attribute created by step 2&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;server.ext(&apos;onPreResponse&apos;, (request, h) =&gt; {
  const response = request.response
  if (!response.isBoom) {
    return h.continue
  }
  const { data } = response
  if (data !== undefined) {
    response.output.payload = {
      ...response.output.payload,
      ...data
    }
  }
  return h.continue
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the above setup, request&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl http://localhost:3000/person -d &apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;would result in&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;statusCode&quot;:400,&quot;error&quot;:&quot;Bad Request&quot;,&quot;message&quot;:&quot;child \&quot;firstName\&quot; fails because [Firstname should not be empty!]&quot;,&quot;errorCode&quot;:111}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default &lt;code&gt;abortEarly: true&lt;/code&gt; is set Hapi, if multiple error codes are desired, only Step 2 needs to be adjusted to&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const server = Hapi.Server({
  // ...
  routes: {
    validate: {
      options: {
        abortEarly: false
      },
      failAction: (request, h, err) =&gt; {
        const errorCodes = err.details
          .map(e =&gt; e.context.errorCode)
          .filter(e =&gt; e !== undefined)
        throw Boom.badRequest(err.message, { errorCodes })
      }
    }
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;request&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl http://localhost:3000/person -d &apos;firstName=&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;would return&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;statusCode&quot;:400,&quot;error&quot;:&quot;Bad Request&quot;,&quot;message&quot;:&quot;child \&quot;firstName\&quot; fails because [Firstname should not be empty!, Firstname should have at least 5 characters!]&quot;,&quot;errorCodes&quot;:[111,121]}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to add error code in your application code, you can simply achieve that by returning a &lt;code&gt;Boom&lt;/code&gt; like the following&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;return Boom.badRequest(&apos;Your error message here&apos;, { errorCode: YOUR_CODE })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;ve composed a &lt;a href=&quot;https://gist.github.com/midnightcodr/c47a1c3322818e9ba42cd264b19885f6&quot;&gt;gist&lt;/a&gt; in case you want to save some typings in trying out the code. Cheers!&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>I just built my first standing desk</title><link>https://blog.endtoend.work/blog/markdown/standing-desk/2014-03-23-i-just-built-my-first-standing-desk</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/standing-desk/2014-03-23-i-just-built-my-first-standing-desk</guid><description>I just built my first standing desk</description><pubDate>Sun, 23 Mar 2014 21:38:00 GMT</pubDate><content:encoded>&lt;h2&gt;So it&apos;s for real&lt;/h2&gt;
&lt;p&gt;I&apos;ve always wanted to build a standing desk using Ikea parts. Other people have done it with Finnvard height adjustable table legs (&lt;a href=&quot;http://www.ikea.com/us/en/catalog/products/00144763/&quot;&gt;http://www.ikea.com/us/en/catalog/products/00144763/&lt;/a&gt;) and a table top. My plan was put off because the Long Island Ikdea store discontinued the Finnvard legs for some time. I was lucky to find them in-store again this weekend and Voilà - my dream desk is finally built:
&lt;img src=&quot;./standing_desk_1.JPG&quot; alt=&quot;standing desk 1&quot;&gt;
&lt;img src=&quot;./standing_desk_2.JPG&quot; alt=&quot;standing desk 2&quot;&gt;
&lt;img src=&quot;./standing_desk_3.JPG&quot; alt=&quot;standing desk 3&quot;&gt;&lt;/p&gt;
&lt;p&gt;I bought this $40 stool just incase I need to take a short break from standing:
&lt;a href=&quot;http://www.ikea.com/us/en/catalog/products/50199215/&quot;&gt;http://www.ikea.com/us/en/catalog/products/50199215/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I also like the fact that I am able to stuff my laser printer onto the shell:
{% img center /images/standing_desk_4.JPG %}&lt;/p&gt;
&lt;h2&gt;Here&apos;s the recipe&lt;/h2&gt;
&lt;p&gt;1 x Linnmon table top, &lt;a href=&quot;http://www.ikea.com/us/en/catalog/products/50251350/&quot;&gt;http://www.ikea.com/us/en/catalog/products/50251350/&lt;/a&gt;, $40&lt;/p&gt;
&lt;p&gt;2 x Finnvard table legs &lt;a href=&quot;http://www.ikea.com/us/en/catalog/products/00144763/&quot;&gt;http://www.ikea.com/us/en/catalog/products/00144763/&lt;/a&gt;, $30 ea&lt;/p&gt;
&lt;p&gt;1 x Tertial Work lamp, &lt;a href=&quot;http://www.ikea.com/us/en/catalog/products/20370383/&quot;&gt;http://www.ikea.com/us/en/catalog/products/20370383/&lt;/a&gt;, $9&lt;/p&gt;
&lt;p&gt;Total: $109+Tax&lt;/p&gt;
&lt;h2&gt;Note&lt;/h2&gt;
&lt;p&gt;I didn&apos;t do some serious hack to increase the height of the table legs like this guy did &lt;a href=&quot;http://www.ikeahackers.net/2014/02/convert-the-finnvard-into-a-height-adjustable-standing-desk.html&quot;&gt;http://www.ikeahackers.net/2014/02/convert-the-finnvard-into-a-height-adjustable-standing-desk.html&lt;/a&gt; because the legs are able to reach upto 36 5/8, with 1&quot; for the table top, the final result is 37 5/8, which is pretty close to my ideal desk height (the height where elbows can rest on the table top).&lt;/p&gt;</content:encoded><h:img src="/_astro/standing_desk_1.Dkg2t8Tl.JPG"/><enclosure url="/_astro/standing_desk_1.Dkg2t8Tl.JPG"/></item><item><title>Install Gitlab(6.4) on Raspberry PI</title><link>https://blog.endtoend.work/blog/markdown/2014-01-11-install-gitlab-6-dot-4-on-raspberry-pi</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2014-01-11-install-gitlab-6-dot-4-on-raspberry-pi</guid><description>Install Gitlab(6.4) on Raspberry PI</description><pubDate>Sat, 11 Jan 2014 14:31:00 GMT</pubDate><content:encoded>&lt;p&gt;I am a big fan of both Raspberry PI and Gitlab so it kinda bugs me when my attempts to install Gitlab onto RPI didn&apos;t succeed because of failure to install therubyracer gem. Others experienced the similar problems I encountered: &lt;a href=&quot;http://www.raspberrypi.org/phpBB3/viewtopic.php?t=32716&amp;#x26;p=397934&quot;&gt;http://www.raspberrypi.org/phpBB3/viewtopic.php?t=32716&amp;#x26;p=397934&lt;/a&gt;, by following most of user dpenezic&apos;s instruction I finally made it to install Gitlab (currently at version 6.4) onto my RPI (512MB Ram but only 384MB is available to the system as I allocate the rest to GPU). So here are what I did:&lt;/p&gt;
&lt;h2&gt;Steps&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Follow &lt;a href=&quot;https://github.com/gitlabhq/gitlabhq/blob/6-4-stable/doc/install/installation.md&quot;&gt;https://github.com/gitlabhq/gitlabhq/blob/6-4-stable/doc/install/installation.md&lt;/a&gt; until &quot;Install Gems&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install libv8 (&lt;a href=&quot;https://github.com/cowboyd/libv8&quot;&gt;https://github.com/cowboyd/libv8&lt;/a&gt;)&lt;/p&gt;
&lt;h1&gt;[update: added git-svn to the list on 1/17/2014]&lt;/h1&gt;
&lt;p&gt;sudo apt-get install -y subversion git-svn
[ -d ~/tmp ] || mkdir ~/tmp
cd ~/tmp
git clone https://github.com/cowboyd/libv8
cd libv8
bundle install&lt;/p&gt;
&lt;h1&gt;be patient, the following command takes a while&lt;/h1&gt;
&lt;p&gt;bundle exec rake clean build binary
sudo gem install pkg/libv8-3.11.8.17-armv6l-linux.gem&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Modify /home/git/gitlab/Gemfile (and .lock) to skip installation of libv8 (as it&apos;s installed through the above step) and the rubyracer&lt;/p&gt;
&lt;p&gt;cd /home/git/gitlab
sudo -u git -H editor Gemfile	# and remove the line: gem &quot;therubyracer&quot;
sudo -u git -H editor Gemfile.lock	# and removed the following lines
libv8 (3.16.14.3)
therubyracer (0.12.0)
libv8 (~&gt; 3.16.14.0)
ref&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install node.js, you can take a look at the script I came up with to compile node.js in RPI: &lt;a href=&quot;https://github.com/midnightcodr/rpi_node_install&quot;&gt;https://github.com/midnightcodr/rpi_node_install&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now you can resume the &quot;Install Gems&quot; step in the Gitlab installation guide&lt;/p&gt;
&lt;p&gt;sudo -u git -H bundle install --deployment --without development test postgres aws&lt;/p&gt;
&lt;h1&gt;and the rest&lt;/h1&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;384MB is not enough to run &quot;Compile assets&quot; step on the gitlab installation guide so I had to add some more swap memory by following &lt;a href=&quot;http://www.cyberciti.biz/faq/linux-add-a-swap-file-howto/&quot;&gt;http://www.cyberciti.biz/faq/linux-add-a-swap-file-howto/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;sudo dd if=/dev/zero of=/swapfile1 bs=1024 count=524288
sudo mkswap /swapfile1
sudo chmod 0600 /swapfile1
sudo swapon /swapfile1&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make sure the server_name setting in /etc/nginx/sites-available/gitlab matches gitlab_url in /home/git/gitlab-shell/config.yml, also add an entry to your RPI&apos;s /etc/hosts&lt;/p&gt;
&lt;p&gt;127.0.0.1	gitlab.server.hostname&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With the current version of Gitlab, performance is not that bad at all - it takes about 2 seconds to switch pages.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>A few zsh tricks</title><link>https://blog.endtoend.work/blog/markdown/2013-10-20-a-few-zsh-tricks</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2013-10-20-a-few-zsh-tricks</guid><description>A few zsh tricks</description><pubDate>Sat, 09 Nov 2013 22:53:00 GMT</pubDate><content:encoded>&lt;p&gt;Here are a few zsh tricks that I learned (and enjoy using) recently:&lt;/p&gt;
&lt;h4&gt;The basics - use !! to retrieve last command&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;tail /var/log/message
!!
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;The not so basic - get last parameter of last command with !$&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mkdir new_folder
cd !$	# after this command you will be in folder new_folder
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Get all parameters from the last command with !*&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;diff src/file1 dest/
cp !*
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;The pretty awsome - substitution with !!:s/&lt;em&gt;from&lt;/em&gt;/&lt;em&gt;to&lt;/em&gt; or !!:gs/&lt;em&gt;from&lt;/em&gt;/&lt;em&gt;to&lt;/em&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;	vi src/en/file.txt
	!!:s/en/fr
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;diff src/en/file.txt dest/en/file.txt
!!:gs/en/fr		# g for global substitution
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Even better with ^&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;	vi src/en/file.txt
	^en^fr

	diff src/en/file.txt dest/en/file.txt
	^en^fr^:G	
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Switching between directories with similar structure (added Dec 7, 2013)&lt;/h4&gt;
&lt;p&gt;let&apos;s say you are in ~/dev/myproject1/src/lib/, and you want to cd to  ~/dev/myproject2/src/lib/, all you need to do is&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd myproject1 myproject2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or even&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd 1 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Batch renaming with zmv&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;	autoload zmv
	zmv &apos;(*).txt&apos; &apos;$1.dat&apos;	# change *.txt to *.dat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;see more zmv examples at &lt;a href=&quot;http://zshwiki.org/home/builtin/functions/zmv&quot;&gt;http://zshwiki.org/home/builtin/functions/zmv&lt;/a&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Use &amp;&amp; to simplify js codes</title><link>https://blog.endtoend.work/blog/markdown/2013-07-20-use-and-and-in-to-simplify-codes</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2013-07-20-use-and-and-in-to-simplify-codes</guid><description>Use &amp;&amp; to simplify js codes</description><pubDate>Sat, 20 Jul 2013 06:46:00 GMT</pubDate><content:encoded>&lt;h2&gt;The technique will be illustrated by the following simple js codes:&lt;/h2&gt;
&lt;p&gt;{% include_code js-reg-and.js %}&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Nagios audio alert with Mac OS X</title><link>https://blog.endtoend.work/blog/markdown/2013-05-09-nagios-audio-alert-with-mac-os-x</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2013-05-09-nagios-audio-alert-with-mac-os-x</guid><description>Nagios audio alert with Mac OS X</description><pubDate>Fri, 10 May 2013 00:50:00 GMT</pubDate><content:encoded>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;You have a nagios server running Linux and you have a nice OS at work called Mac OS X which comes with a handy text-to-speech feature. Wouldn&apos;t it be nice if you can turn your workstation into a nagios audio alert system? In this post I&apos;ll show you extactly how you can achieve that.&lt;/p&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;node.js installed on the client, along with a module called execSync (installed by command &lt;code&gt;npm install -g execSync&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;nc (netcat) is installed on the nagios server (most distro comes with it so this shouldn&apos;t be a problem)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Code on the client&lt;/h2&gt;
&lt;p&gt;{% include_code nagios-audio-alert.js %}
Run the code from terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node /usr/local/bin/nagios-audio-alert.js
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Do some test runs on the client&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;service::PROBLEM::service down test&quot;|nc -u -w 1 127.0.0.1 20123
echo &quot;host::PROBLEM::host test123 is down&quot;|nc -u -w 1 127.0.0.1 20123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If everything goes well, ^C to exit the nagios-audio-alert.js program and move on to nagios server.&lt;/p&gt;
&lt;h2&gt;Modification to nagios server&apos;s config files&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;command.cfg&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;	...
	define command{
			command_name    notify-host-by-tts
			command_line /usr/bin/printf &quot;host::$NOTIFICATIONTYPE$::$HOSTNAME$ is $HOSTSTATE$&quot;|nc -u -v -w 1 &amp;#x3C;replace_with_ip_of_mac_client&gt; 20123
			}
	...
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;template.cfg&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;	...
	define contact{
			name                            generic-contact         ; The name of this contact template
			service_notification_period     24x7                    ; service notifications can be sent anytime
			host_notification_period        24x7                    ; host notifications can be sent anytime
			service_notification_options    u,c,r,f,s               ; send notifications for all service states, flapping events, and scheduled downtime events
			host_notification_options       d,u,r,f,s               ; send notifications for all host states, flapping events, and scheduled downtime events
			#service_notification_commands   notify-service-by-email        ; send service notifications via email
			service_notification_commands   notify-service-by-tts,notify-service-by-email	; notify thru audio &amp;#x26; email
			#host_notification_commands      notify-host-by-email   ; send host notifications via email
			host_notification_commands      notify-host-by-tts,notify-host-by-email
			register                        0                       ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL CONTACT, JUST A TEMPLATE!
			}
	...
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Restart nagios server after making the above changes, always test nagios configuration before restarting: &lt;code&gt;/etc/init.d/nagios checkconfig&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Run nagios-audio-server.js as a daemon&lt;/h2&gt;
&lt;p&gt;Rason for this is that we want the Mac to be able to play alerts even when the user is not logged in. Create &lt;code&gt;/System/Library/LaunchDaemons/nagios-audio-alert.plist&lt;/code&gt; with the following content, change &lt;code&gt;CHANGE_TO_YOUR_USERNAME_ON_MAC&lt;/code&gt; to your mac username
{% include_code nagios-audio-alert.plist.xml %}
then&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo launchctl load -w /System/Library/LaunchDaemons/nagios-audio-alert.plist
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Some notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;20123 is the UDP port I grabbed from the air, change to whatever port you like (make sure they are in sync in both the js code and nagios config file command.cfg&lt;/li&gt;
&lt;li&gt;Reason why execSync is needed is because if you have two (or more) messages come in at the same time (or almost simultaneously), you want the messages to be played one after another&lt;/li&gt;
&lt;li&gt;If you have filewall on either the client or the nagios server, make sure they allow the traffic for the protocol/port used&lt;/li&gt;
&lt;li&gt;A mac client is not really a hard requirement to build a nagios audio alert system like this, a Linux box that is capable of doing text-to-audio can handle this kind of task with ease, some code in nagios-audio-alert.js need to be changed though&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>GA-Z68X-UD3H-B3 F12 Mountain Lion 10.8.3 Installation Notes</title><link>https://blog.endtoend.work/blog/markdown/2013-04-08-ga-z68x-ud3h-b3-f12-mountain-lion-10-dot-8-3-installation-notes</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2013-04-08-ga-z68x-ud3h-b3-f12-mountain-lion-10-dot-8-3-installation-notes</guid><description>GA-Z68X-UD3H-B3 F12 Mountain Lion 10.8.3 Installation Notes</description><pubDate>Tue, 09 Apr 2013 00:02:00 GMT</pubDate><content:encoded>&lt;p&gt;Last Friday I had a hard time installing Moutain Lion 10.8.2 onto a GA-Z68X-UD3H-B3 (BIOS version F10), which was running fine with Lion (10.7.3), with either upgrade or clean install method. With some help from &lt;a href=&quot;http://www.kakewalk.se/forums/discussion/4008/success-ga-z68x-ud3h-10-8-x-mountain-lion/p1&quot;&gt;http://www.kakewalk.se/forums/discussion/4008/success-ga-z68x-ud3h-10-8-x-mountain-lion/p1&lt;/a&gt; and &lt;a href=&quot;http://www.tonymacx86.com/&quot;&gt;http://www.tonymacx86.com/&lt;/a&gt; I managed to install ML 10.8.3 onto the system. Here are the steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make a 10.8.3 Unibeast bootable usb drive (doesn&apos;t need to check either options when making the drive with Unibeast)&lt;/li&gt;
&lt;li&gt;Upgrade BIOS to F12 (tried UEFI version but got BIOS ID check error hence I settled with F12)&lt;/li&gt;
&lt;li&gt;In BIOS setting, make sure HPET is set to 64bit. The system has an Agility 3 120GB SSD so the SATA3 port mode should be set to ACHI&lt;/li&gt;
&lt;li&gt;After installation, boot from Unibeast drive again but choose the OS on the SSD&lt;/li&gt;
&lt;li&gt;Download DDST (to the desktop), Multibeast from tonymacx86.com, choose the followings (More on the audio later)
{% img center /images/z68x-ud3h-b3-f12-ML-10.8.3.png %}&lt;/li&gt;
&lt;li&gt;After Multibeast and Lnx2Mac&apos;s Realtek driver installations, shutdown, remove Unibeast drive and boot from SSD, network should be working but audio is not working (can see the sound icon, can adjust volume but there&apos;s just no sound coming out)&lt;/li&gt;
&lt;li&gt;Download Audio_Network.zip from &lt;a href=&quot;http://ge.tt/6nuMCAL/v/0?c&quot;&gt;http://ge.tt/6nuMCAL/v/0?c&lt;/a&gt;, unzip but only place AppleHDA.kext onto the desktop&lt;/li&gt;
&lt;li&gt;Download KextBeast from tonymacx86.com and run it, it should pick up the AppleHDA.kext on the desktop&lt;/li&gt;
&lt;li&gt;Run Multibeast again, select nothing but Drivers &amp;#x26; Bootloaders-&gt;Drivers-&gt;Audio-&gt;Realtek ALC8xx-&gt;Without DSDT-&gt;ALC889 (basically the same option for audio in step 5)&lt;/li&gt;
&lt;li&gt;Reboot, audio problem should now be fixed (try different line out port if is sound still missing).&lt;/li&gt;
&lt;li&gt;(Optional) If you need to use iMessage, you need to install the newest Chimera (2.0.1 currently) from &lt;a href=&quot;http://www.tonymacx86.com/downloads.php?do=file&amp;#x26;id=164&quot;&gt;http://www.tonymacx86.com/downloads.php?do=file&amp;#x26;id=164&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;other notes&lt;/h2&gt;
&lt;p&gt;Trim doesn&apos;t seem to be turned on when I checked the system info, need to spend some time to look into that.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Cool Audio Reminder script using MAC OS&apos;s text-to-speech</title><link>https://blog.endtoend.work/blog/markdown/2013-01-26-cool-audio-reminder-script-with-mac-os-xs-text-to-speech-feature</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2013-01-26-cool-audio-reminder-script-with-mac-os-xs-text-to-speech-feature</guid><description>Cool Audio Reminder Script With MAC OS Text-to-speech Feature</description><pubDate>Sat, 26 Jan 2013 05:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Why&lt;/h2&gt;
&lt;p&gt;We know that &lt;a href=&quot;http://abcnews.go.com/WN/sitting-long-work-pose-health-danger/story?id=11926874&quot;&gt;sitting for too long is harmful for our health&lt;/a&gt;. My solution to this problem is to set up a friendly reminder using Mac OS X’s speech to text feature, and a bit of programming. So here we go:&lt;/p&gt;
&lt;h2&gt;Steps&lt;/h2&gt;
&lt;p&gt;1). Create a reminder file under your home directory, for example,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ vi ~/break_reminder
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Stand up and walk around Your_Name.
Don&apos;t be lazy Your_Name, I know you can do it.
Stand up now, Your_Name, and do some exercises.
Sitting for too long is not healthy for you Your_Name.
Take a walk, get some water Your_Name.
You need a short break Your_Name.
Don&apos;t stick your butt to the chair for too long Your_Name.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously change Your_Name to something that you want the voice to call you. This is one that I created, feel free to change the content.&lt;/p&gt;
&lt;p&gt;2). Create the reminder shell script&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ vi ~/reminder.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
(( total_lines = $(wc -l &amp;#x3C; ~/break_reminder) )) &amp;#x26;&amp;#x26; (( line = $RANDOM % $total_lines + 1 )) &amp;#x26;&amp;#x26; sed -n ${line}p ~/break_reminder|say
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3). Run some dry tests by running&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash ~/reminder.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4). Add the reminder to crontab&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;crontab -e
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0 9-16 * * 1-5 bash ~/reminder.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will run the reminder script from 9am to 4pm hourly on the clock Monday through Friday. If you are new to crontab, refer to &lt;a href=&quot;http://en.wikipedia.org/wiki/Cron&quot;&gt;http://en.wikipedia.org/wiki/Cron&lt;/a&gt; for more details.&lt;/p&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;I tried putting the meat inside ~/reminder.sh directly into crontab, didn’t work, that’s why this extra script is needed.&lt;/li&gt;
&lt;li&gt;Feel free to change the env from bash to zsh in ~/reminder.sh, I have tested it to work with both bash and zsh.&lt;/li&gt;
&lt;li&gt;Personally I prefer Serena’s voice, you need to download it via these steps: Search for “Dictation &amp;#x26; Speech” in the spotlight -&gt; Tick “Text to Speech” -&gt; Tick “Customize”, then “Serena” from System Voice dropdown, you will be prompted to download the voice file if it has not been downloaded before.&lt;/li&gt;
&lt;li&gt;The reminder (break_reminder) and script (reminder.sh) can be found in my github repository: &lt;a href=&quot;https://github.com/midnightcodr/break_reminder&quot;&gt;https://github.com/midnightcodr/break_reminder&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Display Stand DIY</title><link>https://blog.endtoend.work/blog/markdown/2012-12-30-display-stand-diy</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2012-12-30-display-stand-diy</guid><description>Display Stand DIY</description><pubDate>Mon, 31 Dec 2012 01:50:00 GMT</pubDate><content:encoded>&lt;h2&gt;Ideas&lt;/h2&gt;
&lt;p&gt;Inspired by &lt;a href=&quot;http://lifehacker.com/5872323/diy-ikea-monitor-stand-for-12&quot;&gt;http://lifehacker.com/5872323/diy-ikea-monitor-stand-for-12&lt;/a&gt;, I decided to make a monitor stand that can support 2 LCD monitors.&lt;/p&gt;
&lt;p&gt;I settled down on the following parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The boards: &lt;a href=&quot;http://www.ikea.com/us/en/catalog/products/30094629/#/10094630&quot;&gt;EKBY TRYGGVE softwood shelf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The legs: &lt;a href=&quot;http://www.ikea.com/us/en/catalog/products/20049538/#/00054564&quot;&gt;CAPITA 6&quot; standless stell leg&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since one board needs 6 legs (I wouldn&apos;t try with 4 legs only) I ended up bying 2 boards and 3 packs of legs (12 legs total) so I can make an extra stand for my work place.&lt;/p&gt;
&lt;p&gt;Final material cost (per stand): $25.&lt;/p&gt;
&lt;h2&gt;The making and the result&lt;/h2&gt;
&lt;p&gt;{% img https://dl.dropbox.com/u/16020214/monitor_stand/monitor_stand_in_the_making.jpg %}
{% img https://dl.dropbox.com/u/16020214/monitor_stand/monitor_stand_final.jpg %}&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Install node.js v0.8.14 onto Centos 5 howto</title><link>https://blog.endtoend.work/blog/markdown/2012-11-22-install-node-dot-js-v0-dot-8-14-onto-centos-5-howto</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2012-11-22-install-node-dot-js-v0-dot-8-14-onto-centos-5-howto</guid><description>Install node.js v0.8.14 onto Centos 5 howto</description><pubDate>Fri, 23 Nov 2012 03:04:00 GMT</pubDate><content:encoded>&lt;p&gt;Installing through Linux binaries with the current newest version (v0.8.14) doesn&apos;t seem to work. Attempting to run &quot;node -v&quot; would give the folowing errors&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node: /lib/libc.so.6: version `GLIBC_2.9&apos; not found (required by node)
node: /lib/libc.so.6: version `GLIBC_2.6&apos; not found (required by node)
node: /lib/libc.so.6: version `GLIBC_2.7&apos; not found (required by node)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Therefore the only way to get it installed onto Centos 5.X (which comes with Python 2.4.3 which will fail while compiling node) is through compiling from source code. I am putting together this guide to ensure the installation is painlessly easy as I&apos;ve been through the process a few times. The systems I&apos;ve tested with happened to be both 32bit but I assume the procedures should work with the 64bit system as well, just make sure you download the source files matching the CPU architecture.&lt;/p&gt;
&lt;h2&gt;Steps&lt;/h2&gt;
&lt;p&gt;step 1. build bzip2 from source
{% codeblock lang:bash %}
wget http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz
tar xpzf bzip2-1.0.6.tar.gz
cd bzip2-1.0.6
make
make install
{% endcodeblock %}&lt;/p&gt;
&lt;p&gt;step 2. build Python 2.7
{% codeblock lang:bash %}
wget -O - http://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz|tar xz
cd Python-2.7.3
./configure
make
make install
{% endcodeblock %}&lt;/p&gt;
&lt;p&gt;step 3. log out and log back in to the system&lt;/p&gt;
&lt;p&gt;step 4. build node 0.8.14
{% codeblock lang:bash %}
wget -O - http://nodejs.org/dist/v0.8.14/node-v0.8.14-linux-x86.tar.gz|tar xz
cd node-v0.8.14
./configure
make
make install
{% endcodeblock %}&lt;/p&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Step 1 is very critical for the installation of the node. Without it, you will get Python error &quot;cannot find module bz2&quot; while running make in step 4;&lt;/li&gt;
&lt;li&gt;The solution provided by this guide will install Python 2.7 onto your system. If you found something broken with your Python programs, make sure you check the $PATH setting, the OS stock version should have python under /usr/bin/python while the compiled version should be /usr/local/bin/python. Adjust the orders of /usr/local/bin and /usr/bin in $PATh setting if needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://stackoverflow.com/questions/812781/pythons-bz2-module-not-compiled-by-default&quot;&gt;http://stackoverflow.com/questions/812781/pythons-bz2-module-not-compiled-by-default&lt;/a&gt;
&lt;a href=&quot;http://nodejs.org/download/&quot;&gt;http://nodejs.org/download/&lt;/a&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Three ways to speed up Raspberry Pi</title><link>https://blog.endtoend.work/blog/markdown/2012-11-11-three-ways-to-speed-up-raspberry-pi</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2012-11-11-three-ways-to-speed-up-raspberry-pi</guid><description>Three ways to speed up Raspberry Pi</description><pubDate>Mon, 12 Nov 2012 00:52:00 GMT</pubDate><content:encoded>&lt;p&gt;Raspberry Pi is a fun device to play with but sometimes we wish it can be a tad speedier. In this post I will show you how I did to speed up my rpi. I am running Debian on my rpi so some of the methods might not work for you if you are using different distro.&lt;/p&gt;
&lt;h2&gt;1. Overclock&lt;/h2&gt;
&lt;p&gt;With the newest Debian wheezy, simply run &lt;strong&gt;raspi-config&lt;/strong&gt;, then overclock, OK, choose the one that works for you, choose OK. I settled with 900Mhz because once I use 950MHz, come tasks (such as compilings) could not run properly. Also check out &lt;a href=&quot;http://www.raspberrypi.org/archives/tag/overclocking&quot;&gt;http://www.raspberrypi.org/archives/tag/overclocking&lt;/a&gt; for more information on overclocking rpi.&lt;/p&gt;
&lt;h2&gt;2. Adjust memory split&lt;/h2&gt;
&lt;p&gt;By default I got only 184MB of usuable RAM for the system because the rest of the 256MB goes to GPU. While you are still at raspi-config&apos;s top menu, choose memory_split. If you run your rpi primarily as a headless server (not running X), just pick the one with the highest system RAM option (240), OK.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: method 1 and 2 require restart to take effect.&lt;/p&gt;
&lt;h2&gt;3. Find out auto-start services and de-select those that don&apos;t need to auto-start&lt;/h2&gt;
&lt;p&gt;For example, if you only need mysql server once a while, it makes no sense to make it run when rpi boots. A simple way to find out what processes are started during boot is through a program &lt;strong&gt;sysv-rc-conf&lt;/strong&gt; (not installed by default). Install and fire it up through:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt-get install sysv-rc-conf
sudo sysv-rc-conf
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Generate SSL certificate with 1 easy step</title><link>https://blog.endtoend.work/blog/markdown/2012-10-10-generate-ssl-certificate-with-1-easy-step</link><guid isPermaLink="true">https://blog.endtoend.work/blog/markdown/2012-10-10-generate-ssl-certificate-with-1-easy-step</guid><description>Generate SSL certificate with 1 easy step</description><pubDate>Thu, 11 Oct 2012 02:22:00 GMT</pubDate><content:encoded>&lt;p&gt;Originally I created a post on how to generate ssl certificate with one simple step:
&lt;a href=&quot;http://ricochen.wordpress.com/2010/01/01/generate-ssl-certificate-in-1-quick-step/&quot;&gt;http://ricochen.wordpress.com/2010/01/01/generate-ssl-certificate-in-1-quick-step/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I am here to document the procedure in this first post of my Octopress based blog.&lt;/p&gt;
&lt;p&gt;Command to generate SSL certificate (and its associated private key) is pretty straight-forward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ openssl req -new -x509 -days 3650 -keyout key.pem -out cert.pem -newkey rsa:2048 -subj &quot;/CN=hostname.example.org&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If a password is created in the above step, you&apos;ll need one extra step to remove it unless you want to type the password everytime you restart the web server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ openssl rsa -in key.pem -out key.pem
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>