2013-06-19 23:18

The Two Cable Desktop


It's not pretty yet, but it's functionally complete.

For a long time it has pissed me off the number of things I have to plug into my notebook when I use it on my desktop. After putting some thought into it, I have reduced it to two cables to plug everything I want.

How is it setup?

Cable 1: USB Hub

Yes, you need a large one. I got a 7-port Belkin powered USB Hub, because the ASUS Zenbook has only two USB ports.

Cable 2: Power

I got two power adapters, one is permanently on the desktop, so that I can throw the cable down the back and not have it cross in front. The other lives in my backpack, so I don't have to crawl under the desk to plug/unplug it.


A 2.1 speaker set. These speakers have a volume wheel, which is nice, and they are rather loud, even if I know they are not all that good, they are good enough for me.

To avoid plugging them into the notebook, they are plugged into a bluetooth audio adapter (the white dongle in the USB Hub) which I got out of dx.com for cheap.

I also have a Bluetooth headset (charging in the image), which I can use for phonecalls, hangouts, etc without being tied to the computer.


The combined keyboard/trackpad shown, by Logitech, works with a tiny dongle that's plugged in the hub, so when I plug the hub it "just works", no pairing needed.

It's not really all that much better than the Zenbook's keyboard, but:

  • Having the pad on the right means I don't brush it while typing
  • Having it in the shelf with the Zenbook on the table makes for better ergonomics.


This is actually a wired network. There is a USB/Ethernet adapter plugged in the hub. That lets me use the high priority port in my router. Plus I don't lose the adapter.

Device Charging

I can charge up to 4 devices. The headset has a weird cable, so it's plugged to the hub. When not charging, it drops behind the table.

The red extensible cables, I got from dx.com (they are, very cheap) and they are awesome.

  • They retract, so when not in use they don't tangle.
  • They are both mini and micro USB cables, because the head has both connectors. They were also 30-pin old iPhone cables, but I didn't need that, so I removed the connectors.

Since the hub is powered, I can charge all the devices even when the notebook is not there.

Random Devices

I still have the right-side USB port free, so I can plug random things there, like the golden 200x USB microscope shown in the image, or a USB plasma ball.


Nothing very strange, but I think I achieved a very clean setup, where I can plug my notebook into all the peripherals in 10 seconds and turn it into a "desktop" with a minimum of annoyance.

2013-06-19 15:41

MinCSS is amazing

I had this issue open in the bug tracker for Nikola (my static site generator) for a long time: "Add mincss support".

Well, no, it doesn't have it yet, but I did some research on whether it would be worth adding. And boy, mincss impressed the heck out of me.

You see, Nikola's themes tend to use unadultered bootstrap, which means they carry a large number of things that are not used in their CSS. Besides, it uses several stylesheets from docutils, pygments, and more.

What mincss does is examine your HTML and your CSS, and remove all the unused CSS. So, I wrote a script that examines the Nikola output and overwrites the CSS files with the minimal things that are actually needed there.

And the result?

Here is the before/after for each CSS file in Nikola's demo site:

bootstrap-responsive.min.css  16849  3251
bootstrap.min.css            106059 14737
code.css                       3670  2114
colorbox.css                   6457   774
rst.css                        6559  2581
theme.css                      1287  1061
                             140881 24518

But wait, Nikola supports bundling all those files into a single large CSS file to avoid network requests (using webassets). Does it work in that case too?

Well yes:

all-nocdn.css                167457 29496

But that is not all. The mincss files are not minified. Passing all-nocdn.css through Yui-compressor shrinks it further to 20599 bytes. Which, gzipped, is a paltry 4801 bytes. That means the complete styling of the whole site is a single CSS file less than 5KB in size.

That, is impressive.

2013-06-03 00:31

Deploying Django Into My Cheap VPS

I am preparing to open my cheap site-and-blog-hosting service to the public at some point, so I needed to do some groundwork into deployment. Consider that the host that will run it will have very limited resources, so I needed to find lean and cheap solutions when possible, but at the same time, I want to achieve reasonable reliability and ease of deployment.

Since this is a testing server, I want it to have git master deployed. I don't want automatic deployment, but I want to deploy often, meaning several times daily.

I preferred simple tools instead of complex tools, lightweight tools with just enough features instead of heavier, more fully-featured tools. Your choices on each step could and probably should be different than mine, depending on your situation, requirements and personal preferences.

So, here's my notes from how it's done currently. This is not meant as a HOWTO, just a description of what seems to be working well enough so far.

Read more…

2013-05-29 10:06

Javascript Makes Me Cry: Turning a Date into a String

Working late last night in Alva I wanted to do something that sounded trivial:

When the page loads, get the current date and time, and if a certain input is empty, put it there like this:

28/05/2013 23:45

So, how hard can that be, right? Well not hard, but...

Getting the current date-time is easy: now = new Date(); So, is there something like strftime in Javascript? Of course not. You can get code from the usual places and have a untested, perhaps broken, limited version of it. And I am not about to add a strftime implementation to use it once. Sure, there are a number of Date methods that convert to strings, but none of them lets you specify the output format. So, let's try to do this The Javascript Way, right?

To get the elements I want to put in the value, I used accessor methods. So, obviously, these should give me what I want for the string, right?

now.getDay(), now.getMonth(), now.getYear(), now.getHour() now.getMinute()

Well, they are, at the date mentioned above, respectively: 2, 4, 113, error, error

Ok, the errors are easy to fix from the docs. It's actually getHours() and getMinutes(), so now we have 2, 4, 113, 23, 45 and of those five things, the last two are what one would expect, at least. Let's go over the other three and see why they are so weird:

Date.getDay() returned 2 instead of 28
Because getDay() gives you the week day and not the day of the month. Which is absolutely idiotic. So, you have to use getDate() instead. Which means the name is a lie, becasue the logical thing for getDate() to return is the whole date.
Date.getMonth() returned 4 instead of 5
Because getMonth() returns months in the [0,11] range. Which is beyond idiotic and bordering in evil. Come on, Javascript, people have been referring to may as "5" for nearly two thousand years now! What other language does this? Anyone knows one?
Date.getYear() returned 113 instead of 2013
Because it uses offset-from-1900. Which is amazing, and I had never heard of a language doing in a standard type. Because why? So, use getFullYear() instead.

Now, armed with the right 5 numbers, let's format it. Does Javascript have the equivalent of sprintf or format ? Of course not. In JavaScript, without 3rd party modules, you create strings by addition, like a caveman. Again, I know I could add a format method to the String prototype and make this work, but I am not adding an implementation of format or sprintf just to use it once!

So, this produces that I want:

now.getDate()+'/'+(now.getMonth()+1)+'/'+now.getFullYear()+' '+now.getHours()+':'+now.getMinutes()

Unless... the day or month are lower than 10, in which case it's missing the left-padding zero. Luckily, for the purpose I was using it, it worked anyway. Because OF COURSE there's no included function to left-pad a string. You have to do it by addition. Or, of course, add a 3rd party function that's out there, in the internet, somewhere.

2013-05-29 09:15

Nothing Ever Really Goes Away On The Internet: ra-plugins

I used to manage a large number of QMail installations. And because Qmail was ... weirdly licensed, I wrote a set of plugins that ran on top of a patch called Qmail-SPP. I pretty much stopped doing that years ago because life took me in other directions, and forgot all about it.

That collection is called ra-plugins and I had not touched it since late 2008.

And today... I got a patch with two whole plugins to add to it so that it makes Qmail handle email addresses more like Gmail does (aliases using user+foo and making user.foo the same as userfoo).

So, I got them, added them, fixed a few simple building issues, updated the libsmtp it uses internally for one of the plugins to a later version, and there it stays, perhaps not to be touched until 2018.

2013-05-28 14:26

(Re)Introducing Alva, a Nikola Server

Over a year ago (time flies!) I posted something about a project called Alva. Let me quote myself:

Alva is almost the opposite of Nikola. If Nikola is about making static sites, Alva is a dynamic site. However, as Hegel suggests, from the thesis and the antithesis comes the synthesis.

So, Alva is about dynamically creating static sites. If you want to have Nikola in your server instead of in your own computer, and have the convenience of an online tool, that's the niche Alva tries to fill.

So, you would install Alva, and use it like any other web-based blogging tool. Yet, behind the scenes, you would have Nikola, and all the performance and security benefits of static sites.

And maybe someday, I (or someone) will put up a multi-user version of Alva, and you will be able to get hosted blogs, knowing all the data is yours and you can leave anytime and do your own thing.

The approach I was taking at the time proved to be unsuccessful, and there were a few other failures along the way. Of course, the problem was in how I was approaching the task. So I did the right thing, and learned how to do it "right".

Still not usable, still not hosted anywhere, but already semi-functional: Alva lives now

There's a lot of work still to be done. But I now know how to do it. To prevent the usual arguments, here is a little explanation of motivation, tooling, etc.


I want a way to host blogs very cheaply. How cheaply? I want at least 1000 reasonably active users in a $5 VPS. That would make Alva a reasonable alternative to hosted multi-user wordpress, which means it would be a reasonable solution (if setup is easy enough) for small-to-medium organizations which don't want to setup expensive infrastructure yet want to own their data (think schools, small businesses, FLOSS projects, etc.) I also want to provide that service, for free. Which is why another reason I want it to be super cheap.

How does Alva help provide this super-cheap blog hosting?

  1. It needs to scale following the number of edits not views.
  2. If it gets too busy with edits, changes take longer to appear, but the site itself doesn't get any slower.
  3. Editing and serving can be provided by separate services, so I can use some super-fast static file server and a super-configurable WSGI deployment.
  4. Individual pages can be heavily optimized so that they download fast


One of the guiding principles here is that to deliver this sort of thing, in my spare time, the development process needs to be stingy with the most limited resource: me. I can't spend a lot of me here. I need to be careful and not over-promise.

So, whenever there was a 3rd-party tool that saves a significant amount of time, that's what I am using.

Because it has a much stronger 3rd-party toolset than Flask or any micro-framework. For example, the Flask equivalent of django-allauth broke my will to live. Because the admin interface means I can start adding data to see if it makes sense before I write all the required views.
Because I don't want you to have to create accounts here unless you want to, this provides (optional) social login and registration. This was easy to setup and works almsost out-of-the-box
Bootstrap and Django-bootstrap-toolkit
Nikola is already heavily invested in bootstrap, so it just made sense to go further down that road. I understand bootstrap, and django-boostrap-toolkit is easy enough (although I can't make their datepicker work)
Because fighting is boring.
Because Django's mechanisms to find templates and static files are many and confuse me.
Redis + RQ + django-rq
It's crucial for the whole approach to use job queues in order to detach the rendering from the actual Django app. This combination makes job dispatching ridiculously easy, setup is trivial (install everything, start redis, a few lines of config, ./manage.py rqworker and off you go) and they provide a django admin page where I can see the failed jobs, which is awesome.
Because it's easy enough, and allows me some freedom exploring data organization in my models without committing to it forever or recreating databases with test data all the time.
I will probably serve the generated sites via gatling just like my current sites because it has the simplest named domain configuration possible, it's fast and very light in resource usage.
A cool, simple editor with live previews that supports almost every markup. Not WYSIWYG or even WYSIWYM so possibly I will have to add an alternative. I started using django-markitup but it's not a good idea (it uses a old version of markitup which requires JQuery < 1.9) and am in the process of just using Markitup manually.

So, feel free to give Alva a try and/or give me a hand, comments welcome.

2013-05-24 13:30


Esto es para ayudarme en una charla que voy a dar en el PyDay Rosario por favor respondan honestamente!


2013-05-15 19:46

Endomondo Lied To Me

A week ago I re-started my diet and exercise plan. Since I am grossly overweight, the exercise plan is basically "walk around 5km every day, fat guy". Since I am a nerd, I wanted data so I could stop lying to myself about how much I was walking.

I had seen Endomondo mentioned in my tweeter timeline a bunch of times and the featureset looked pretty much exactly as I needed:

  • Track my walking
  • Keep history
  • Show it in google maps (because it's nice)

It even did things like tracking calories burnt and so on.

The only problem was... it really sucks at figuring out how much you walked. It consistently overestimates by around 50% the distances, and since it calculates the average speed based on time and distance (and the time measurement is correct) it overestimates speed by 50%, which then means it overestimates calories burnt by (I am guessing) 125%.

How did I verify that Endomondo is wrong, and avoid the obvious explanation of "your GPS is broken"?

  1. I tracked myself using Endomondo and Google Trails at the same time.
  2. I counted steps roman-mile style (count every "left-right", multiply by 1.6)
  3. I measured the path I walked in Google Maps and Bing Maps

All those measurements tell me a walk of 1100m +/- 150m is measured by Endomondo as 1.68 km

Ver mapa más grande

Why does this happen? I could assume Endomondo is just crap, and probably be right, but trying to come up with a "interesting" explanation, I am leaning towards noisy measurements. For example, if Endomondo saw my position shifting randomly 10 or 15 meters left or right it would probably add enough noise to make the path 50% longer (for a much more fun example of this, read this paper (by no other than Benoit Mandelbrot!) but this doesn't explain why Google Trails works so much better (unless Trails does something smart with antialiasing and interpolation).

If you use Endomondo, care to share your experience? I am reluctant to 1-star it in Google Play without independent confirmation.

2013-05-08 13:40

How I Made My Phone Useful

So, I got this phone on my trip to the US. It's a Galaxy SII variant called the S959G. It's not a very common variant, and of course it was locked to a network that's unavailable here, and comes with Android 2.3 which is ancient, so it needed some fixing.

And man, the Android community is a mess about documenting stuff. So here's my shot at it.

This only applies to THIS PHONE if you go blind doing this, or your dog catches fire, not my problem.

The goal:

  • Unlocked
  • Rooted
  • Newer Android
  • Google Apps
  • Done using Ubuntu (because I prefer it)

The process is a bit lengthy, but simple.

Install heimdall
It's in the repos, just use apt, piece of cake

Go to the XDA page for this phone because that seems to be the canonical information source.

Get (from where it says "The best way to root") the CWM RECOVERY v6.0.27 file. It's called CWM-Recovery.tar.md5 because version names are for the weak.

Get the recovery.bin that's inside it using tar xvf CWM-Recovery.tar.md5

Put your phone into download mode. For this model that's done like this:

  • Turn off
  • Plug into USB
  • Press Volume Up and Volume Down (keep pressed)
  • Press power (keep pressed)
  • When Samsung appears on screen, let go of power button
  • When you see a warning sign, let go of volume buttons
  • Click Volume Up

Flash the recovery.bin using heimdall: heimdall flash --recovery recovery.bin

Now you have to go into recovery to make backups:

  • Turn off
  • Unplug USB
  • Press Volume up and down and hold
  • Press power
  • Let go of power when Samsung flashes
  • There you are

In this mode, volume up/down move the cursor up/down and the power button chooses the selected option. Do the obvious thing to do a backup.

Turn on the phone and make sure it works.

Now copy the CyanogenMod zip file from XDA (or any i777 image) into the phone's SD Card somehow (drag and drop in Nautilus works ;-)

Go back into recovery mode as above.

Clean up the phone: Clean data, cache, and in advanced, clean dalvik cache.

Install by using "Install zip from sdcard" and choosing the right one, and doing the obvious thing.

Boot the phone, it should now be in CyanogenMod (yay).

Try to make a phone call. If you can't and/or the phone asks for a SIM PIN, it's still locked. To unlock I used this APK which was untested on this model, but hey, it worked.

You may also need to install one of the "modem" files from XDA. Just put them in the SD card, go to recovery, and install one, try, if it doesn't work then try another one.

Get gapps-jb-20130301-signed.zip from the XDA page.

Copy that into the SD card, then install from recovery mode.

And that's it. If it fails, you can probably go back to something reasonable using the stock firmware that's in the same page, but I have not done it so I don't offer intructions.

Good luck!

2013-05-06 16:17

A Oakland me fuí, y traje remeras

Cuando te postulás en Canonical, casi siempre dice algo como "está dispuesto a viajar internacionalmente varias veces al año". Eso es parte de estar en una empresa donde el 90% de la gente trabaja desde la casa. Que cuando querés verte en persona con algún grupo, el lugar más práctico puede estar a 10341KM de distancia.

Entonces hice mi valijita porque trato de nunca despachar equipaje, y me subí al vuelo de United a Houston, donde conectaría a San Francisco, y de ahí me tomaría el tren a Oakland. Bueno, ese era el plan.

Me dí un pequeño lujo, hice upgrade a "Economy Plus", no es super caro y garantiza que llegás con las rótulas puestas, pero no mucho más que eso.

El avión a Houston era un 767 nuevito, con pantallas de 9" y enchufe USB para cargar tus aparatitos. El asiento de adelante estaba a mas o menos un metro y medio del mío. De hecho, con mis bracitos de cocodrilo tenía que estirarme mucho para llegar al bolsillo de adelante. Y mi bonita pantalla individual... no funcionaba. No me registraba los toques. El azafato muy amablemente me reseteó el sistema dos veces pero no hubo caso, así que el asiento bárbaro, pero bueno, siempre algo sale mal en los viajes.

Y hablando de que las cosas salen mal... la conexión en Houston era un poco justa de tiempo. Y había mucha, mucha gente en migraciones, y muy muy poca gente atendiendo en migraciones así que el avión salió mientras yo estaba haciendo cola todavía.

Así que me voy al mostrador, me dicen que el siguiente vuelo sale a las 3PM y llega a San Francisco a las 5PM (por el cambio de hora). Como le digo que tengo entradas para ver Warriors-Nuggets a las 7, el copado que me atendió me pone en standby en todos los vuelos a San Francisco del día, así apenas hubiera un asiento viajo, porque "anything for a NBA fan, dude". Así que engancho en el primer vuelo, que SÍ es un upgrade porque en vez de viajar en esos horrendos A310 es otro 767!

Obvio, voy en la última fila (pero pasillo :-) y la pantallita anda, y me ví This is 40 que está muy buena aunque los protagonistas son increíblemente odiosos y (por tener 41) me dan ganas de agarrarlos a trompadas para sacarlos de la nube de pedos.

Como en los vuelos internos no hay morfi, me compré una "lunch box" que te venden. Los contenidos de la mía incluían:

  • Un pote de puré de manzana
  • Un minipaquete de Oreo
  • Un minipaquete de skittles
  • Dos (2) "meat sticks sabor peperoni"
  • Dos (2) galletitas de agua
  • Un pote de "mediterranean tapenade"
  • Un minipaquete de galletitas Goldfish

Eso, obviamente es una comida balanceada. Balanceada sobre el filo de una navaja entre "What the fuck" y "uhhhh, oreos".

Sí, gané el torneo de trivia del avión y tengo foto que no publico por modestia. A la vuelta hice mejor puntaje pero no saqué fotos porque fuí el único que jugó.

Llego a San Francisco, espero los 15 minutos que necesito para despejarme del mareo que me provoca casi cualquier medio de locomoción, y me compré un teléfono en una máquina expendedora, un Samsung Galaxy SII "El teléfono Android más esperado de 2011" que está lindo (todas las fotos de este post están hechas con ese teléfono) pero parece ser una variante porque no tiene botón "Home" como veo en las fotos de la web.

Como todavía estaba intrigado/hambreado por la experiencia "lunchbox" me comí unas buffalo wings (no hay nada que hacerle, no me gusta comer alitas, no sé para que las pedí) y me dieron una rica sopa de almejas de yapa (viene con unas galletitas que las rompés y las metés adentro), y un café horrible.

Ante la perspectiva de una hora de tren, decidí hacerme el playboy y tomarme un taxi. Tomé varios en este viaje, y eran: Hindú (2), Pakistaní (1), Afgano (1), Uzbeko (1), Hondureño (1).

Llego al hotel (lindo, es un Marriott, son todos iguales), lo encuentro a mi compañero de habitación Lucio Torre cometiendo crímenes contra la gardelidad con "Por una cabeza" (igual, mejoró mucho en la semana)

Por una cabeza by Carlos Gardel on Grooveshark

Nos fuimos al Starbucks a comer/tomar algo.


Universos paralelos, donde Hellman's hace mostaza.

Ducha, tratar de parecer un ser humano, minisiesta, y me voy a imprimir la entrada para el partido al "Business Center", y nunca más claro el porqué del nombre.


¡Pagué el mínimo!

Llego al Oracle Arena, que es famoso por ser el estadio más ruidoso de la NBA. Entré un cachito tarde así que me perdí la presentación de los equipos, pero bueno. TODO el estadio vestido de amarillo... porque había remeras gratis en cada asiento. Y sí, la mía estaba ahí esperándome.


Un argentino en Oakland.


Traje 2, regalos para las mellizas Warrior!

El estadio está bueno, mi asiento estaba bárbaro, medio arriba como para poder ver toda la cancha, no tan lejos que no se vea nada, cerca del pasillo para salir rápido, no en el pasillo, para que no me joda la gente pasando todo el tiempo (perdón por el momento Sheldon). Colgando del techo hay por supuesto una super pantalla, tableros, etc, y... dos diminutas bolas de espejos.



El partido muy bueno, el ambiente muy ruidoso, todos muy entusiasmados aunque todo con esa artificialidad que tiene el que el tablero del centro diga que hay que gritar y todos griten lo mismo (excepto "Ref you suck!" que es espontáneo), un groso Stephen Curry, tira desde la casa si quiere, impresiona que hasta los que parecen lentos en la tele son rapidísimos. Andrew Bogut en un momento estaba con las manos en las rodillas como pidiendo aire, miré para otro lado y en 2 segundos había cruzado la cancha y volcado como un animal.

Termina el partido...


Sí, ganaron los Warriors, por paliza.

Salgo, encaro para el tren, y veo un taxi parado, me lleva, resulta que el tipo fue a ver el partido y el taxi estaba estacionado nomás, pero me llevó igual porque "ten bucks is ten bucks, man!"


Este cartel estaba a 5 metros del pibe que me vendió la segunda remera, obviamente.

Vuelvo al hotel, me dió hambre, hice mi primera parada en Rosamunde Bar & Grill, un lugar especializado en salchichas!


"First time? try something basic": Beer Sausage, sauerkraut & hot peppers.

Volvimos otro día con Lucio, y casi nos morimos, pero ... muy buenas las salchichas, tienen una docena de variedades, y son todas muy ricas :-)

Al día siguiente salí a pasear un poco bien temprano, porque durante el día laburamos, resulta que el hotel está a tres cuadras de la comisaría de Oakland (o, mejor dicho, del condado de Alameda)


Esto es enfrente de la entrada, los demás estacionan a la vuelta...


El único negocio del centro de Oakland que no cierra a las 9PM.

Así encontré el lugar donde desayunaría casi todos los días, Caffé 817. ¿Por qué? Rico café, ricas cosas para acompañarlo, muebles steampunk, mesa en la vereda...


Ahí empezamos a laburar, con lo que paso a tener mucho menos para contar, porque llegaba a la noche tan hecho pomada que casi no salimos. Eso sí, esas tres cuadras alrededor del hotel las conozco como la palma de mi mano!


El mundo se divide entre los que te ven comiendo esto y te miran con cara de horror, y los que se quejan que no les guardaste.


El 1 de mayo cuando salgo del hotel justo estaba pasando la manifestación correspondiente. Resulta que el año pasado se agarraron con la cana, así que estaban muy vigilados. De hecho eran unas cuantas personas paseando por la calle principal haciendo ruido con mas o menos el mismo número de policías. Sí, la mitad van encapuchados, todos los negocios van cerrando, pero todo tranqui, por lo menos cuando pasé yo. Después vendían galletitas en la plaza.





Fuí a la farmacia, compré un regalo para mi otra cuñada:


De nada, María!

Paseamos un poco en auto un día (O sea, fuimos a Best Buy, y a Target) y esto está en el estacionamiento:


También hay carteles parecidos en los lugares adonde podés fumar. Que no incluyen nada a menos de 25 pies de una puerta. Sí, está prohibido fumar en la vereda.

Paseamos un poco más fuimos a la Jack London Square, comimos en varios lugares por ahí, fuí al cine dos veces (Iron Man 3 y Oblivion) y se acabó el viaje. Para volver al aeropuerto no había taxi, y me dice el tipo de uniforme de la puerta "Take a Towncar, it costs almost the same" y me lleva a una cosa que es algo intermedio entre una limo y una carroza fúnebre:


Es cómodo, para que te lo voy a negar.

Viaje de vuelta sin grandes problemas (asiento que no reclinaba), ya que hacía ya dos días que nadie abría fuego con armas automáticas en el aeropuerto llegada a casa, The End.

Más fotos (incluyendo queso Filadelfia de chocolate, simulador de tornados, estatua de Jack London, otras comidas, etc.) en la galería.

Contents © 2000-2019 Roberto Alsina