diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f04f75 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +_site +.sass-cache +.jekyll-metadata +/nbproject \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..7c05f73 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +romkevandermeulen.nl \ No newline at end of file diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..8260b44 --- /dev/null +++ b/_config.yml @@ -0,0 +1,31 @@ +# Site settings +title: Romke van der Meulen +email: romke.vd.meulen@gmail.com +description: A Dutch student of Man and Machine, Science and Faith, Philosophy and Life. +baseurl: "" +url: "https://romkevdmeulen.nl" +twitter_username: RomkeVdMeulen +github_username: RomkeVdMeulen +medium_username: RomkeVdMeulen + +# Build settings +markdown: kramdown +highlighter: rouge + +gems: + - jekyll-gist + - jekyll-paginate + +exclude: [nbproject] + +# Homepage +paginate: 50 + +# Defaults +defaults: + - + scope: + path: "" # an empty string here means all files in the project + values: + layout: "post" + comments: true diff --git a/_drafts/delete-account-the-important-button-that-everyone-forgets.md b/_drafts/delete-account-the-important-button-that-everyone-forgets.md new file mode 100644 index 0000000..e69de29 diff --git a/_includes/disqus.html b/_includes/disqus.html new file mode 100644 index 0000000..675b4c0 --- /dev/null +++ b/_includes/disqus.html @@ -0,0 +1,16 @@ +{% if page.comments %} +
+ + +{% endif %} diff --git a/_includes/footer.html b/_includes/footer.html new file mode 100644 index 0000000..5a736f3 --- /dev/null +++ b/_includes/footer.html @@ -0,0 +1,35 @@ + diff --git a/_includes/head.html b/_includes/head.html new file mode 100644 index 0000000..b946d9b --- /dev/null +++ b/_includes/head.html @@ -0,0 +1,13 @@ + + + + + + {% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %} + + + + + + + diff --git a/_includes/header.html b/_includes/header.html new file mode 100644 index 0000000..e9010a5 --- /dev/null +++ b/_includes/header.html @@ -0,0 +1,20 @@ + diff --git a/_includes/icons/clock-o.svg b/_includes/icons/clock-o.svg new file mode 100644 index 0000000..09269ef --- /dev/null +++ b/_includes/icons/clock-o.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_includes/icons/github.svg b/_includes/icons/github.svg new file mode 100644 index 0000000..4422c4f --- /dev/null +++ b/_includes/icons/github.svg @@ -0,0 +1 @@ + diff --git a/_includes/icons/medium.svg b/_includes/icons/medium.svg new file mode 100644 index 0000000..f865372 --- /dev/null +++ b/_includes/icons/medium.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/_includes/icons/tag.svg b/_includes/icons/tag.svg new file mode 100644 index 0000000..12efb70 --- /dev/null +++ b/_includes/icons/tag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_includes/icons/twitter.svg b/_includes/icons/twitter.svg new file mode 100644 index 0000000..dcf660e --- /dev/null +++ b/_includes/icons/twitter.svg @@ -0,0 +1 @@ + diff --git a/_includes/post-nav.html b/_includes/post-nav.html new file mode 100644 index 0000000..8083a4e --- /dev/null +++ b/_includes/post-nav.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/_includes/share/share-icon-facebook.svg b/_includes/share/share-icon-facebook.svg new file mode 100644 index 0000000..3337723 --- /dev/null +++ b/_includes/share/share-icon-facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_includes/share/share-icon-googleplus.svg b/_includes/share/share-icon-googleplus.svg new file mode 100644 index 0000000..8ea68fa --- /dev/null +++ b/_includes/share/share-icon-googleplus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_includes/share/share-icon-hn.svg b/_includes/share/share-icon-hn.svg new file mode 100644 index 0000000..6829ec0 --- /dev/null +++ b/_includes/share/share-icon-hn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_includes/share/share-icon-linkedin.svg b/_includes/share/share-icon-linkedin.svg new file mode 100644 index 0000000..551c9a8 --- /dev/null +++ b/_includes/share/share-icon-linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_includes/share/share-icon-reddit.svg b/_includes/share/share-icon-reddit.svg new file mode 100644 index 0000000..9557bc9 --- /dev/null +++ b/_includes/share/share-icon-reddit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_includes/share/share-icon-twitter.svg b/_includes/share/share-icon-twitter.svg new file mode 100644 index 0000000..abfab4e --- /dev/null +++ b/_includes/share/share-icon-twitter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_includes/share/share.html b/_includes/share/share.html new file mode 100644 index 0000000..409aa82 --- /dev/null +++ b/_includes/share/share.html @@ -0,0 +1,36 @@ +
+

Share what you've learned

+ + +
diff --git a/_includes/user-github.html b/_includes/user-github.html new file mode 100644 index 0000000..7ddc795 --- /dev/null +++ b/_includes/user-github.html @@ -0,0 +1 @@ +{% include icons/github.svg %}{{ include.username }} diff --git a/_includes/user-medium.html b/_includes/user-medium.html new file mode 100644 index 0000000..19ebf5d --- /dev/null +++ b/_includes/user-medium.html @@ -0,0 +1 @@ +{% include icons/medium.svg %}{{ include.username }} diff --git a/_includes/user-twitter.html b/_includes/user-twitter.html new file mode 100644 index 0000000..d18fae4 --- /dev/null +++ b/_includes/user-twitter.html @@ -0,0 +1 @@ +{% include icons/twitter.svg %}{{ include.username }} diff --git a/_layouts/base.html b/_layouts/base.html new file mode 100644 index 0000000..c4a6eb9 --- /dev/null +++ b/_layouts/base.html @@ -0,0 +1,17 @@ + + + + {% include head.html %} + + +
+ {% include header.html %} + +
+ {{ content }} +
+ + {% include footer.html %} +
+ + diff --git a/_layouts/page.html b/_layouts/page.html new file mode 100644 index 0000000..6a285e1 --- /dev/null +++ b/_layouts/page.html @@ -0,0 +1,16 @@ +--- +layout: base +--- +
+ + {% if page.title %} +
+

{{ page.title }}

+
+ {% endif %} + +
+ {{ content }} +
+ +
diff --git a/_layouts/post.html b/_layouts/post.html new file mode 100644 index 0000000..3798de3 --- /dev/null +++ b/_layouts/post.html @@ -0,0 +1,23 @@ +--- +layout: base +--- +
+ + {% include post-nav.html %} + +
+

{{ page.title }}

+ +
+ +
+ {{ content }} +
+ + {% include share/share.html title=page.title %} + + {% include post-nav.html %} + + {% include disqus.html %} + +
diff --git a/_posts/2015-01-29-getting-the-latest-version-of-docker-on-ubuntu.md b/_posts/2015-01-29-getting-the-latest-version-of-docker-on-ubuntu.md new file mode 100644 index 0000000..a42bcea --- /dev/null +++ b/_posts/2015-01-29-getting-the-latest-version-of-docker-on-ubuntu.md @@ -0,0 +1,13 @@ +--- +title: "Getting the latest version of Docker on Ubuntu" +date: 2015-01-29 +tags: + - docker + - ubuntu +excerpt: Here's how you get the latest version of Docker on Ubuntu. +--- +If you’re on Ubuntu you have the option of installing +[Docker](https://www.docker.com/) simply through `apt-get`, but you’ll end up +with a rather old version. To get the latest version, run this: + +{% gist 66d73eed71021af6a6bc %} diff --git a/_posts/2015-01-29-trying-out-shipyard-on-your-local-machine.md b/_posts/2015-01-29-trying-out-shipyard-on-your-local-machine.md new file mode 100644 index 0000000..d402c02 --- /dev/null +++ b/_posts/2015-01-29-trying-out-shipyard-on-your-local-machine.md @@ -0,0 +1,90 @@ +--- +title: "Trying out Shipyard on your local machine" +date: 2015-01-29 +tags: + - docker + - shipyard +--- +**Note: this tutorial is outdated.** +Check [shipyard-project.com](http://shipyard-project.com/) for the latest +installation instructions. + +[Shipyard](http://shipyard-project.com/) is a management console for +[Docker](https://www.docker.com/). You can use it to manage deployment of docker +images and containers on a number of different machines, but in this post I'm +going to show you how to try out shipyard on just your local machine. Be aware +that installing shipyard opens up a number of ports on your machine. Make sure +these aren't accessible to the public. + +* 49153 - rethinkDB instance +* 49154 - rethinkDB cluster +* 49155 - rethinkDB web interface +* 4243 - Docker interface +* 8080 - Shipyard web interface + +First make sure you have +[the latest version of Docker installed]({{ site.baseurl }}/2015/01/29/getting-the-latest-version-of-docker-on-ubuntu.html) +(the version in Ubuntu's repos is often too stale for shipyard). In this setup, +based on the [shipyard quickstart guide](http://shipyard-project.com/docs/quickstart/), +we'll be running shipyard inside a docker container. That means it can't access +the docker daemon on your machine directly. So we have make docker listen on +port `4243` so that shipyard can access it: + +{% highlight bash %} +sudo sh -c "echo 'DOCKER_OPTS=\"-H tcp://:4243 -H unix:///var/run/docker.sock\"' >> /etc/default/docker" +{% endhighlight %} + +Now we can run shipyard simply by pulling the necessary docker images from the +central index and running them: + +{% highlight bash %} +docker run -it -d --name shipyard-rethinkdb-data \ + --entrypoint /bin/bash shipyard/rethinkdb -l + +docker run -it -P -d --name shipyard-rethinkdb \ + --volumes-from shipyard-rethinkdb-data \ + --restart=always shipyard/rethinkdb + +docker run -it -p 8080:8080 -d --name shipyard \ + --link shipyard-rethinkdb:rethinkdb \ + --restart=always shipyard/shipyard +{% endhighlight %} + +Your shipyard is now up and running. You can access the web interface at  +[http://localhost:8080](http://localhost:8080). We can also access shipyard by +CLI. For this we simply run another container. You might want to save this +command to a script or alias to make it easier to remember. + +{% highlight bash %} +docker run --rm -it shipyard/shipyard-cli +{% endhighlight %} + +Once you're on the CLI, you need to login to the shipyard instance we've just +setup. Remember: the CLI is running inside a separate container, with no direct +access to either your host machine or the shipyard server. To access either, we +need to use your host machine's public or local network IP address. You can find +the latter by running `ifconfig` and looking at the `inet addr` for your network +connection (usually `eth0`). + +Run `shipyard login`. For host use the IP address you just found and port `8080`. +E.g.: [http://192.168.1.10:8080](http://192.168.1.10:8080/). The default user +is `admin` with password`shipyard`. Once you've logged into the CLI or Web +interface, we still need to connect our localhost docker daemon to shipyard +(remember that shipyard can't directly access your machine from inside its +container). In the web interface you can go to the engines tab. From the CLI you +can run this: + +{% highlight bash %} +shipyard add-engine --id 'localhost' \ + --addr 'http://[your-ip]:4243' \ + --cpus '1.0' --memory '1024' \ + --label 'local' --label 'dev' +{% endhighlight %} + +Now that you have your shipyard set up and pointed at your local machine, you +should already be able to see several running containers, like shipyard itself. +You can add new containers by clicking “Deploy”. If you want, you can also +manage other servers running docker by adding them to the engines. But be sure +to secure your connection! +[This post]({{ site.baseurl }}/2015/01/30/setting-up-a-secure-public-connection-for-a-docker-daemon.html "Setting up a secure public connection for a Docker daemon") +will tell you how to expose the docker daemon securely. diff --git a/_posts/2015-01-30-setting-up-a-secure-public-connection-for-a-docker-daemon.md b/_posts/2015-01-30-setting-up-a-secure-public-connection-for-a-docker-daemon.md new file mode 100644 index 0000000..0140f7e --- /dev/null +++ b/_posts/2015-01-30-setting-up-a-secure-public-connection-for-a-docker-daemon.md @@ -0,0 +1,22 @@ +--- +title: "Setting up a secure public connection for a Docker daemon" +date: 2015-01-30 +tags: + - docker + - shipyard + - nginx +--- +I've posted about [how to set up shipyard on your local machine]({{ site.baseurl }}/2015/01/29/trying-out-shipyard-on-your-local-machine.html "Trying out Shipyard on your local machine"). +You can also use your local shipyard to manage your remote servers, but to do +this you have to set up a secure connection to the Docker daemon on your server. +Docker has [an article](https://docs.docker.com/engine/security/https/) which tells you +how to do this. Since I've had to do it a couple of times over the last few +days, I figured I'd make a bash script for the process. + +{% gist c04464b9730a7f01d27a %} + +Once you've set this up, you can add your server's Docker daemon to shipyard in +the engine tab. Use the URL you passed to the script in the "Address" field with +port `4243`, e.g. `https://example.com:4243`. Copy the contents of  +`/etc/docker/cert.pem` to "SSL Certificate", `/etc/docker/key.pem` to "SSL Key" +and `/etc/docker/ca.pem` to "CA Certificate". diff --git a/_posts/2015-01-31-dokku-docker-server-power-control.md b/_posts/2015-01-31-dokku-docker-server-power-control.md new file mode 100644 index 0000000..6c046f6 --- /dev/null +++ b/_posts/2015-01-31-dokku-docker-server-power-control.md @@ -0,0 +1,234 @@ +--- +title: "Dokku and Docker on the same server: power and control" +date: 2015-01-31 +tags: + - docker + - dokku + - mysql + - mariadb + - nginx + - ubuntu + - wordpress +--- +[Docker](https://www.docker.com) is a platform that allows you to deploy any +kind of app in a uniform way. [Dokku](http://progrium.viewdocs.io/dokku) builds +on this to create a full PaaS. It allows you to simply push your code repository +to your server and let Dokku build and deploy it for you, automatically. + +While Dokku automatic builds are powerful and awesome, sometimes you want a bit +more control over how your app is deployed. For instance, you may want to deploy +your app in one container and link it with another container running your +database. Or you want to use one of the vast array of Docker images available in +the [Docker registry](https://registry.hub.docker.com/). + +Dokku has a number of [community plugins](http://progrium.viewdocs.io/dokku/plugins#user-content-community-plugins "Dokku: community plugins") +that go a long way toward making such a scenario possible. Still, it doesn't +feel like the right tool for the job. After all, one of the greatest advantages +of Docker is that you can run similar or even identical containers on your +production server and on your development environment, ensuring painless +deploying. Dokku means giving up control of how your app will be deployed. Which +is great for simple applications or for testing, but for serious production use +you'll probably want that control back. + +Let's say I have a modest number of projects I want to deploy, so I figure a +single server should be enough. I want to use pre-defined Docker images to +deploy my projects in production on this server. At the same time I also want to +have Dokku running on it so I can quickly push some new code to it to see if it +will work. It's taken some research to find how to effectively use Docker and +Dokku on a single server. Here's what you do. + +## Setting up the server + +I created a VPS on [DigitalOcean](https://www.digitalocean.com/?refcode=bd051b4d8067)  +using their pre-defined Dokku image. It turned out to be very up-to-date, with +the latest stable versions of Docker and Dokku running. If you're running your +own server, you can install the latest Docker +[like this]({{ site.baseurl }}/2015/01/29/getting-the-latest-version-of-docker-on-ubuntu.html "Getting the latest version of Docker on Ubuntu"), +and install Dokku using the +[install instructions](https://github.com/dokku/dokku#installing). + +## Deploying a simple app with Dokku + +After installing Dokku, you also need to specify on which domain you want Dokku +to deploy new apps. Write the domain name, e.g. `mydomain.com`, to +`/home/dokku/VHOST`. Now that Dokku is up and running, we need to register the +SSH key of our development machine so that we can push our code to Dokku. On +your development machine run this: + +{% highlight shell %} +cat .ssh/id_rsa.pub| ssh root@mydomain.com sshcommand acl-add dokku myname +{% endhighlight %} + +( The `myname` is to keep track of that key in case you want to delete it later. ) + +Now we're ready to use Dokku. To try it out, check out a sample +[node.js](http://nodejs.org/ "NodeJS") project on your development machine and +then push it to Dokku: + +{% highlight shell %} +git clone git@github.com:heroku/node-js-sample.git +cd node-js-sample +git remote add dokku dokku@mydomain.com:test +git push dokku master +{% endhighlight %} + +Now you should see something like this: + + Counting objects: 381, done. + Delta compression using up to 4 threads. + Compressing objects: 100% (308/308), done. + Writing objects: 100% (381/381), 210.18 KiB | 0 bytes/s, done. + Total 381 (delta 49), reused 373 (delta 44) + -----> Cleaning up ... + -----> Building test ... + -----> Adding BUILD_ENV to build environment... + -----> Node.js app detected + -----> Requested node range: 0.10.x + -----> Resolved node version: 0.10.35 + -----> Downloading and installing node + -----> Exporting config vars to environment + -----> Installing dependencies + + ... + + -----> Running post-deploy + -----> Creating new /home/dokku/test/VHOST... + -----> Configuring test.mydomain.com... + -----> Creating http nginx.conf + -----> Running nginx-pre-reload + Reloading nginx + =====> Application deployed: + http://test.mydomain.com + +And, like it says right there, your application is now deployed to +`http://test.mydomain.com`! + +## Deploying a Wordpress blog with Docker + +Alright, so now we've seen how Dokku can build our apps for us. Now let's try +deploying a pre-existing Docker image: we'll deploy a blog using the +[official Wordpress Docker image](https://registry.hub.docker.com/_/wordpress/). +But before you can set up a new blog, you'll first need to set up a MySQL +database server. Here is how I did it: + +{% highlight shell %} +docker run --name mysql --restart=always \ + -e MYSQL_ROOT_PASSWORD=some-secret-string -d mariadb +{% endhighlight %} + +The `--restart=always` will ensure that the Docker daemon starts up the container +again after an error or reboot. The Wordpress container can set up its own +database if you give it root access to the DB server, but I wanted to try doing +it myself. Rather than running a mysql client in another container, I installed +it directly on my server: + +{% highlight shell %} +{% raw %} +apt-get install -qqy mysql-client +mysql -h`docker inspect --format "{{ .NetworkSettings.IPAddress }}" mysql` \ + -uroot -p +{% endraw %} +{% endhighlight %} + +The code between backticks retrieves the IP address assigned to my db server +container so I can connect to it. +\EDIT: [I've found a more elegant way to do this]({{ site.baseurl }}/2015/02/13/easy-access-dockerized-mysql-server.html "Easy access to a dockerized mysql server") +Now I can set up a database for my new blog by hand: + +{% highlight mysql %} +CREATE DATABASE myblog; +CREATE USER 'myblog'@'%' IDENTIFIED BY 'another-password'; +GRANT ALL ON myblog.* TO 'myblog'@'%'; +FLUSH PRIVILEGES; +{% endhighlight %} + +Now we're ready to deploy our Wordpress blog: + +{% highlight shell %} +docker run --name myblog --link mysql:mysql \ + -e WORDPRESS_DB_USER=myblog -e WORDPRESS_DB_PASSWORD=another-password \ + -e WORDPRESS_DB_NAME=myblog -e VIRTUAL_HOST=blog.mydomain.com \ + --restart=always -d wordpress +{% endhighlight %} + +Now our blog container is running, as we can see by running `docker ps`. +However, we can only access its port `80` directly from the server. The +`VIRTUAL_HOST` env shows where we really want to access our new blog, but it's +not working yet. + +## Reverse-proxy with nginx using docker-gen + +We should already have [nginx](http://nginx.org/en/) running on our server since +Dokku depends on it. It wouldn't be hard to find the IP address of our blog +container and have nginx act as a reverse proxy for it, using our domain name. +The problem is that if our blog container is restarted, e.g. after a reboot of +our server, its IP address may have changed and we have to update our nginx +config by hand. Fortunately, somebody has been here before us and has created a +solution: [docker-gen](http://jasonwilder.com/blog/2014/03/25/automated-nginx-reverse-proxy-for-docker/ "Automated Nginx Reverse Proxy for Docker"). +It's a tool that automatically builds and updates config files for all running +containers. Let's install this wonderful tool: + +{% highlight shell %} +cd /tmp +wget https://github.com/jwilder/docker-gen/releases/download/0.3.6/docker-gen-linux-amd64-0.3.6.tar.gz +tar xzf docker-gen-linux-amd64-0.3.6.tar.gz +mv docker-gen /etc/nginx/docker-gen +{% endhighlight %} + +Now we have to create a template for the nginx config we want to generate. I +based mine on [jwilder's nginx-proxy](https://github.com/jwilder/nginx-proxy/blob/master/nginx.tmpl). +Write your config to `/etc/nginx/docker.template`. Now we can run docker-gen as +a single command. But if we want it to run automatically whenever our server +reboots, then we'll have to install it as a service. To do so, write this script +in `/etc/nginx/docker-gen-service`: + +{% highlight bash %} +#!/bin/bash +/etc/nginx/docker-gen -only-exposed -watch -notify "service nginx reload" \ + /etc/nginx/docker.template /etc/nginx/sites-enabled/docker_containers +{% endhighlight %} + +And make it executable: `chmod +x /etc/nginx/docker-gen-service`. Now we'll +write some upstart config to `/etc/init/docker-nginx.conf`: + +{% highlight conf %} +# docker-nginx - Nginx config generator for Docker containers + +description "Nginx config generator for Docker containers" +author "Somebody " + +# When to start the service +start on filesystem and started docker + +# When to stop the service +stop on runlevel [016] + +# Automatically restart process if crashed +respawn + +# Send output to logfile +console log + +# Run before process +pre-start script + [ -d /etc/nginx/certs ] || mkdir -p /etc/nginx/certs +end script + +# Start the process +exec /etc/nginx/docker-gen-service +{% endhighlight %} + +And now we start our new service: `initctl start docker-nginx`. Now docker-gen +is keeping an eye on all our Docker containers, and updating our nginx config to +match. To do this, it looks at the `VIRTUAL_HOST` env for each container. +Remember that for our Wordpress container we specified +`-e VIRTUAL_HOST=blog.mydomain.com` (if you want to assign more than one +hostname to a single container, you can use a comma-separated list: +`VIRTUAL_HOST=blog.a.com,test.b.com`). So provided our DNS settings are correct, +we should now be able to visit `http://blog.mydomain.com` and be greeted by the +Wordpress configuration screen. + +* * * + +So there you have it: you can now quickly and easily deploy an app using Dokku, +while at the same time deploying production apps more precisely with Docker. diff --git a/_posts/2015-02-04-renderable-simple-template-inheritance-php.md b/_posts/2015-02-04-renderable-simple-template-inheritance-php.md new file mode 100644 index 0000000..8c5306d --- /dev/null +++ b/_posts/2015-02-04-renderable-simple-template-inheritance-php.md @@ -0,0 +1,16 @@ +--- +title: "Renderable: simple template inheritance in PHP" +date: 2015-02-04 +excerpt: + I wrote this PHP class some time ago. I've used it in a number of tools I've + written in PHP. It gives you a simple yet versatile templating mechanism. +tags: + - php + - templating +--- + +I wrote this PHP class some time ago. I've used it in a number of tools I've +written in PHP. It gives you a simple yet versatile templating mechanism. See +below for an example of use. + +{% gist e0a36ff2c65cc61d448c %} diff --git a/_posts/2015-02-05-suspend_until-suspend_for.md b/_posts/2015-02-05-suspend_until-suspend_for.md new file mode 100644 index 0000000..1bddfbd --- /dev/null +++ b/_posts/2015-02-05-suspend_until-suspend_for.md @@ -0,0 +1,14 @@ +--- +title: "suspend_until and suspend_for" +date: 2015-02-05 +tags: + - bash + - shell +--- +Here are two Bash scripts I wrote some years ago that allow you to send your +computer to sleep for a specified time and then wake up automatically. They work +with the `rtcwake` and `pm-suspend` tools. + +{% gist 534851ddd3553d2d9eeb %} + +{% gist c41ba98bf6ff014ff4a5 %} diff --git a/_posts/2015-02-05-update-projects-sh.md b/_posts/2015-02-05-update-projects-sh.md new file mode 100644 index 0000000..ef23669 --- /dev/null +++ b/_posts/2015-02-05-update-projects-sh.md @@ -0,0 +1,17 @@ +--- +title: "Update your projects: up.sh" +date: 2015-02-05 +tags: + - bash + - shell + - svn + - git + - development +--- +I have a number of frameworks, platforms and reference projects in my workspace +that I want to update daily. Rather than going to each of them and doing `svn up` +or `git pull` by hand, I've written a little Bash script to do it for me. I just +keep a list of all projects I want updated. Now I need only run `./up.sh` once a +day. + +{% gist 5978f733f6d48542fa62 %} diff --git a/_posts/2015-02-07-programming-language.md b/_posts/2015-02-07-programming-language.md new file mode 100644 index 0000000..f00bd68 --- /dev/null +++ b/_posts/2015-02-07-programming-language.md @@ -0,0 +1,21 @@ +--- +title: "Which programming language?" +date: 2015-02-07 +tags: + - development +--- +I happened to come across this fine article today: +"[What Technology Should My Startup Use?](http://matt.aimonetti.net/posts/2013/08/27/what-technology-should-my-startup-use/)" +by [Matt Aimonetti](http://matt.aimonetti.net/). + +C#, Java, PHP, Python, Ruby... With so many programming languages around I, like +many others, have asked myself which one to use where. An experienced programmer +can build great applications in almost any language. Most modern programming +languages are more alike than different. So the question of which language to +use becomes less about the structure and features of the language and more about +the design philosophies, infrastructure and communities behind them. Matt gives, +as far as I can tell, an accurate portrayal of the 'atmosphere' surrounding many +modern languages. Though his focus is on startups, the information is also +useful for a developer looking which skills to develop. Good high-level  +descriptions of the programmer landscape are difficult to come by in my +experience, so I highly recommend you read this article. \ No newline at end of file diff --git a/_posts/2015-02-13-easy-access-dockerized-mysql-server.md b/_posts/2015-02-13-easy-access-dockerized-mysql-server.md new file mode 100644 index 0000000..7666cd3 --- /dev/null +++ b/_posts/2015-02-13-easy-access-dockerized-mysql-server.md @@ -0,0 +1,41 @@ +--- +title: "Easy access to a dockerized mysql server" +date: 2015-02-13 +excerpt: + Deploying a new mysql server using Docker is quite easy. But once the server + is up, you want root access to it from your command-line. Here's a little + shell function that'll do it for you. +tags: + - docker + - shell + - mysql + - mariadb +--- +Deploying a new mysql server using Docker is quite easy: + +{% highlight shell %} +docker run --name mysql mariadb +{% endhighlight %} + +But once the server is up, you want root access to it from your command-line. +The [mariadb repo](https://registry.hub.docker.com/_/mariadb/) shows an example +of how to do this. I've wrapped their command in a little shell function that +you can add to your `.bashrc` or `.zshrc`. If you call the function without +arguments, it will drop you on the mysql prompt. If you give a mysql command as +argument, it will execute it as mysql root and show the result. + +{% gist b84d07b8f3f7bfd73f03 %} + +Here's how it works: + + # mysqlroot "SHOW DATABASES" + Database + my_db_1 + my_db_2 + + # mysqlroot + MariaDB [(none)]> + +## Bonus round: automated backup + +{% gist a74abb55b1c246474a4b %} diff --git a/_posts/2015-02-19-docker-gen-automatic-nginx-config-with-a-human-touch.md b/_posts/2015-02-19-docker-gen-automatic-nginx-config-with-a-human-touch.md new file mode 100644 index 0000000..d761883 --- /dev/null +++ b/_posts/2015-02-19-docker-gen-automatic-nginx-config-with-a-human-touch.md @@ -0,0 +1,269 @@ +--- +title: "docker-gen: automatic nginx config with a human touch" +date: 2015-02-19 +tags: + - docker + - nginx + - mariadb + - mysql + - upstart + - wordpress +--- +In this article I will demonstrate a setup using +[Docker](http://www.docker.com), +[docker-gen](https://github.com/jwilder/docker-gen) and +[nginx](http://nginx.org/en/) +that will add new Docker containers to nginx automatically as an upstream +instruction, but still let you manually configure how these containers are +publicly served by writing the site instructions yourself. + +---- + +## Background +In [an earlier article]({{ site.baseurl }}/2015/01/31/dokku-docker-server-power-control/ "Dokku and Docker on the same server: power and control") +I set up my server with Docker and [Dokku](https://github.com/progrium/dokku). +One of the things I showed is how to deploy Docker containers on your server and +serve them on a specific URL by using nginx as a reverse proxy. I automated this +by using docker-gen. Docker-gen watches your Docker containers, and +automatically generates nginx config when new containers are deployed or taken +down. I used a [config template](https://github.com/jwilder/nginx-proxy/blob/master/nginx.tmpl) +from [jwilder](https://github.com/jwilder)'s [nginx-proxy](https://github.com/jwilder/nginx-proxy)  +container, which works pretty simply: you set an environmental `VIRTUAL_HOST` on +your container, and docker-gen creates all the necessary nginx config to host +your container on this URL. For example: + +{% highlight shell %} +docker run \ + -e WORDPRESS_DB_USER=foo -e WORDPRESS_DB_PASSWORD=bar \ + -e WORDPRESS_DB_NAME=foo -e VIRTUAL_HOST=blog.foo.com \ + --link mysql:mysql --restart=always wordpress +{% endhighlight %} + +This command is all you need to run (provided you've already set up your +database of course) to deploy a new Wordpress blog and serve it on blog.foo.com. +However, after having used this process a number of times, I found that it has a +couple of drawbacks: + +* I can't change the URL my container is hosted at: I have to destroy and +re-create the container with a different `VIRTUAL_HOST` value +* I can't configure nginx for individual containers: every rule I add has to +go into my configuration template and is then applied to every nginx site that +docker-gen generates + +Fortunately, there is a simple solution for this. The nginx config generated by +this template consists of two components: an upstream and a site. The simple +solution then is to have docker-gen generate the upstream instructions, but +write the site by hand. This method is flexible enough to deal with the +container's randomly assigned IP address yet still allows you to configure your +site any way you want. Here is the new way to deploy a Wordpress blog. I create +a Wordpress container and give it a new environmental: `NGINX_UPSTREAM` rather +than `VIRTUAL_HOST`: + +{% highlight shell %} +docker run \ + -e WORDPRESS_DB_USER=foo -e WORDPRESS_DB_PASSWORD=bar \ + -e WORDPRESS_DB_NAME=foo -e NGINX_UPSTREAM=fooblog \ + --name fooblogcontainer --link mysql:mysql \ + --restart=always wordpress +{% endhighlight %} + +docker-gen automatically updates the config file +`/etc/nginx/conf.d/docker_upstream_hosts.conf`: + +{% highlight conf %} +upstream fooblog { + # fooblogcontainer + server 172.17.0.42:80; +} +{% endhighlight %} + +Now I write the file `/etc/nginx/sites-available/com.foo.blog` by hand: + +{% highlight conf %} +server { + server_name blog.foo.com; + + access_log /var/log/nginx/com.foo.blog_access.log; + error_log /var/log/nginx/com.foo.blog_error.log notice; + + include global/restrictions.conf; + include global/wordpress.conf; + + location / { + proxy_pass http://fooblog; + } +} +{% endhighlight %} + +I link to this new file from `/etc/nginx/sites-enabled` and then reload nginx. +Now my new Wordpress site is available at blog.foo.com. Yes, it is more work +than simply starting the container with `VIRTUAL_HOST=blog.foo.com` to begin +with, but this way allows me to apply additional nginx configuration that +applies to Wordpress blogs but no other web applications. The two included files +for example are config files with additional security rules and additional +Wordpress rules respectively. You want this, right? Otherwise you wouldn't still +be reading this. Don't worry! I'm going to show you how I did it. + +## Setup + +### VIRTUAL_HOST + +I wanted to keep the old setup that triggers on `VIRTUAL_HOST` as well as the +new mechanism, so I moved around some files. I kept the docker-gen executable in +`/etc/nginx/docker-gen` (see [my previous article]({{ site.baseurl }}/2015/01/31/dokku-docker-server-power-control/ "Dokku and Docker on the same server: power and control")). +I moved the config template (which is a straightforward copy of +[jwilder's nginx-proxy template](https://github.com/jwilder/nginx-proxy/blob/master/nginx.tmpl)) +to `/etc/nginx/docker-gen-vhost-template`. The command that runs docker-gen with +this template is embedded in a script at `/etc/nginx/docker-gen-vhost-service` +with this content: + +{% highlight bash %} +#!/bin/bash +/etc/nginx/docker-gen -only-exposed -watch -notify "service nginx reload" \ + /etc/nginx/docker-gen-vhost-template \ + /etc/nginx/sites-available/docker_virtual_hosts +{% endhighlight %} + +I put the output config file in `sites-available` and link to it from +`sites-enabled` so that I can disable auto-generated site config if needed. Now +I make an upstart job for this script (feel free to use another service manager) +in `/etc/init/docker-virtualhosts.conf`: + +{% highlight conf %} +# docker-virtualhosts - Nginx config generator for Docker containers +# using the VIRTUAL_HOST env + +description "Nginx vhost config generator for Docker containers" +author "Sombody " + +start on filesystem and started docker +stop on runlevel [016] +respawn +console log + +pre-start script + [ -d /etc/nginx/certs ] || mkdir -p /etc/nginx/certs +end script + +exec /etc/nginx/docker-gen-vhost-service +{% endhighlight %} + +Now I simply run `initctl start docker-virtualhosts` and my config generator +service is up. + +## NGINX_UPSTREAM + +The setup that triggers on `NGINX_UPSTREAM` is even simpler. I wrote this config +template to `/etc/nginx/docker-gen-upstream-template`: + +{% raw %} + {{ range $host, $containers := groupByMulti $ "Env.NGINX_UPSTREAM" "," }} + + upstream {{ $host }} { + {{ range $container := $containers }} + {{ $addrLen := len $container.Addresses }} + {{/* If only 1 port exposed, use that */}} + {{ if eq $addrLen 1 }} + {{ with $address := index $container.Addresses 0 }} + # {{$container.Name}} + server {{ $address.IP }}:{{ $address.Port }}; + {{ end }} + {{/* If more than one port exposed, */}} + {{/* use the one matching VIRTUAL_PORT env var */}} + {{ else if $container.Env.VIRTUAL_PORT }} + {{ range $address := .Addresses }} + {{ if eq $address.Port $container.Env.VIRTUAL_PORT }} + # {{$container.Name}} + server {{ $address.IP }}:{{ $address.Port }}; + {{ end }} + {{ end }} + {{/* Else default to standard web port 80 */}} + {{ else }} + {{ range $address := $container.Addresses }} + {{ if eq $address.Port "80" }} + # {{$container.Name}} + server {{ $address.IP }}:{{ $address.Port }}; + {{ end }} + {{ end }} + {{ end }} + {{ end }} + } + + {{ end }} +{% endraw %} + +Then I wrote the script `/etc/nginx/docker-gen-upstream-service`: + +{% highlight bash %} +#!/bin/bash +/etc/nginx/docker-gen -only-exposed -watch -notify "service nginx reload" \ + /etc/nginx/docker-gen-upstream-template \ + /etc/nginx/conf.d/docker_upstream_hosts.conf +{% endhighlight %} + +And created an upstart job in `/etc/init/docker-upstreams.conf` exactly like the +last one but executing `/etc/nginx/docker-gen-upstream-service`. The output +config file `/etc/nginx/conf.d/docker_upstream_hosts.conf` is loaded by default +by nginx, so I can reference the generated upstreams from my hand-written site +instructions like I've shown you. In case you're curious, the additional config +files I used in my site example look like this: +`/etc/nginx/global/restrictions.conf`: + +{% highlight conf %} +# Global restrictions configuration file. +# Designed to be included in any server {} block. +location = /favicon.ico { + log_not_found off; + access_log off; +} + +location = /robots.txt { + allow all; + log_not_found off; + access_log off; +} + +# Deny all attempts to access hidden files such as .htaccess, .DS_Store (Mac). +# Keep logging the requests to parse later +location ~ /\. { + deny all; +} +{% endhighlight %} + +And `/etc/nginx/global/wordpress.conf`: + +{% highlight conf %} +# Deny access to any files with a .php extension in the uploads directory +# Works in sub-directory installs and also in multisite network +# Keep logging the requests to parse later +location ~* /(?:uploads|files)/.*\.php$ { + deny all; +} + +client_max_body_size 8M; +{% endhighlight %} + +You may also want to include the `restrictions.conf` file in your `VIRTUAL_HOST` +config template as well. So, if you've been following along with this tutorial, +you should have these files: + + /etc/init/docker-virtualhosts.conf + /etc/init/docker-upstreams.conf + /etc/nginx/docker-gen + /etc/nginx/docker-gen-upstream-service + /etc/nginx/docker-gen-upstream-template + /etc/nginx/docker-gen-vhost-service + /etc/nginx/docker-gen-vhost-template + /etc/nginx/global/restrictions.conf + /etc/nginx/global/wordpress.conf + /etc/nginx/sites-enabled/docker_virtual_hosts + -> /etc/nginx/sites-available/docker_virtual_hosts + +These files should be generated automatically: + + /etc/nginx/conf.d/docker_upstream_hosts.conf + /etc/nginx/sites-available/docker_virtual_hosts + +And to deploy blog.foo.com you should write +`/etc/nginx/sites-available/com.foo.blog` and link to it from +`/etc/nginx/sites-enabled/`. diff --git a/_sass/_base.scss b/_sass/_base.scss new file mode 100644 index 0000000..10007dc --- /dev/null +++ b/_sass/_base.scss @@ -0,0 +1,201 @@ +/** + * Reset some basic elements + */ +body, h1, h2, h3, h4, h5, h6, +p, blockquote, pre, hr, +dl, dd, ol, ul, figure { + margin: 0; + padding: 0; +} + + + +/** + * Basic styling + */ +html { + height: 100%; +} + +body { + font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family; + color: $text-color; + background: $background-color url(../img/bg.jpg); + -webkit-text-size-adjust: 100%; + -webkit-font-feature-settings: "kern" 1; + -moz-font-feature-settings: "kern" 1; + -o-font-feature-settings: "kern" 1; + font-feature-settings: "kern" 1; + font-kerning: normal; + height: 100%; +} + + + +/** + * Set `margin-bottom` to maintain vertical rhythm + */ +h1, h2, h3, h4, h5, h6, +p, blockquote, pre, hr, +ul, ol, dl, figure, +%vertical-rhythm { + margin-bottom: $spacing-unit / 2; +} + + + +/** + * Images + */ +img { + max-width: 100%; + vertical-align: middle; +} + + + +/** + * Figures + */ +figure > img { + display: block; +} + +figcaption { + font-size: $small-font-size; +} + + + +/** + * Lists + */ +ul, ol { + margin-left: $spacing-unit; +} + +li { + > ul, + > ol { + margin-bottom: 0; + } +} + + + +/** + * Headings + */ +h1, h2, h3, h4, h5, h6 { + font-weight: $base-font-weight; +} + + + +/** + * Links + */ +a { + color: $link-color; + text-decoration: none; + + &:visited { + color: $link-visited-color; + } + + &:hover { + color: $link-hover-color; + text-decoration: underline; + } +} + + + +/** + * Blockquotes + */ +blockquote { + color: $blockquote-color; + border-left: 4px solid $blockquote-border-color; + padding-left: $spacing-unit / 2; + font-size: 18px; + letter-spacing: -1px; + font-style: italic; + + > :last-child { + margin-bottom: 0; + } +} + + + +/** + * Code formatting + */ +code { + padding: 1px 5px; + word-wrap: break-word; +} + +pre { + font-size: 15px; + color: $code-text-color; + background-color: $code-background-color; + border: 1px solid $code-border-color; + border-radius: 3px; + padding: 8px 12px; + overflow-x: auto; + + > code { + border: 0; + padding-right: 0; + padding-left: 0; + } +} + + + +/** + * Wrapper + */ +#wrapper { + min-height: 100%; + width: $content-width; + margin: 0 auto; + background-color: $background-color; + position: relative; + z-index: $z-index-wrapper; + @include clearfix(); + + @include media-query($on-laptop) { + width: auto; + } +} + + + +/** + * Icons + */ +svg { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + fill: $icon-fill-color; +} + +.icon--medium > svg { + path:nth-child(1) { + fill: #d0d2d3; + } + path:nth-child(2) { + fill: #a6a8ab; + } + path:nth-child(3) { + fill: #808184; + } + path:nth-child(4) { + fill: #58595b; + } +} diff --git a/_sass/_index.scss b/_sass/_index.scss new file mode 100644 index 0000000..9f24d77 --- /dev/null +++ b/_sass/_index.scss @@ -0,0 +1,21 @@ +#index { + + article { + padding-bottom: $spacing-unit / 2; + margin-bottom: $spacing-unit * 3; + + header { + margin-bottom: $spacing-unit / 2; + h2 { + margin-bottom: 0; + } + span { + font-size: 0.8em; + padding-right: 15px; + } + } + p { + margin-bottom: 0; + } + } +} \ No newline at end of file diff --git a/_sass/_layout.scss b/_sass/_layout.scss new file mode 100644 index 0000000..e3975dc --- /dev/null +++ b/_sass/_layout.scss @@ -0,0 +1,253 @@ +/** + * Site header + */ +.site-header { + height: 130px; + background: transparent url(../img/books.jpg) center center / cover no-repeat; + + // Positioning context for the mobile navigation icon + position: relative; +} + +.site-title { + position: absolute; + bottom: 0; + left: 20px; + font-size: 42px; + font-weight: 200; + letter-spacing: -1px; + line-height: 29px; + margin: 0; + padding: 0; + color: $background-color; + &:active, &:visited, &:hover { + color: $background-color; + text-decoration: none; + } +} + +.site-nav { + float: right; + line-height: 56px; + + a, a:visited { + color: $background-color; + } + + .menu-icon { + display: none; + } + + .page-link { + line-height: $base-line-height; + margin-right: 20px; + } + + @include media-query($on-palm) { + position: absolute; + top: 9px; + right: $spacing-unit / 2; + background-color: $nav-background-color; + border: 1px solid $nav-border-color; + border-radius: 5px; + text-align: right; + + .menu-icon { + display: block; + float: right; + width: 36px; + height: 26px; + line-height: 0; + padding-top: 10px; + text-align: center; + + > svg { + width: 18px; + height: 15px; + + path { + fill: $menu-icon-fill-color; + } + } + } + + .trigger { + clear: both; + display: none; + } + + &:hover .trigger { + display: block; + padding-bottom: 5px; + } + + a, a:visited { + color: $text-color; + } + + .page-link { + display: block; + padding: 5px 10px; + margin-right: 0; + } + } +} + + + +/** + * Site footer + */ +.site-footer { + margin: $spacing-unit; + padding-top: $spacing-unit; + border-top: 1px solid $divider-color; +} + +.footer-heading { + font-size: 18px; + margin-bottom: $spacing-unit / 2; +} + +.contact-list, +.social-media-list { + list-style: none; + margin-left: 0; + + @include media-query($on-palm) { + li { + margin-top: 10px; + + a { + font-size: 1.2em; + } + } + } +} + +.footer-col-wrapper { + font-size: 15px; + margin-left: -$spacing-unit / 2; + @include clearfix(); +} + +.footer-col { + float: left; + margin-bottom: $spacing-unit / 2; + padding-left: $spacing-unit / 2; +} + +.footer-col-1 { + width: -webkit-calc(50% - (#{$spacing-unit} / 2)); + width: calc(50% - (#{$spacing-unit} / 2)); +} + +.footer-col-2 { + width: -webkit-calc(50% - (#{$spacing-unit} / 2)); + width: calc(50% - (#{$spacing-unit} / 2)); +} + +@include media-query($on-laptop) { + .footer-col-1, + .footer-col-2 { + width: -webkit-calc(50% - (#{$spacing-unit} / 2)); + width: calc(50% - (#{$spacing-unit} / 2)); + } + + .footer-col-3 { + width: -webkit-calc(100% - (#{$spacing-unit} / 2)); + width: calc(100% - (#{$spacing-unit} / 2)); + } +} + +@include media-query($on-palm) { + .footer-col { + float: none; + width: -webkit-calc(100% - (#{$spacing-unit} / 2)); + width: calc(100% - (#{$spacing-unit} / 2)); + } +} + + + +/** + * Page content + */ +.page-content { + padding: $spacing-unit; +} + +.page-heading { + font-size: 20px; +} + +.post-list { + margin-left: 0; + list-style: none; + + > li { + margin-bottom: $spacing-unit; + } +} + +.post-meta { + font-size: $small-font-size; + color: $post-meta-color; +} + +.post-link { + display: block; + font-size: 24px; +} + + + +/** + * Posts + */ +.post-header { + margin-bottom: $spacing-unit; +} + +.post-title { + font-size: 42px; + letter-spacing: -1px; + line-height: 1; + + @include media-query($on-laptop) { + font-size: 36px; + } +} + +.post-content { + margin-bottom: $spacing-unit; + + h2 { + font-size: 30px; + + @include media-query($on-laptop) { + font-size: 26px; + } + } + + h3 { + font-size: 24px; + + @include media-query($on-laptop) { + font-size: 20px; + } + } + + h4 { + font-size: 18px; + + @include media-query($on-laptop) { + font-size: 16px; + } + } +} + +#disqus_thread { + border-top: 1px solid $divider-color; + padding-top: $spacing-unit; +} diff --git a/_sass/_mixins.scss b/_sass/_mixins.scss new file mode 100644 index 0000000..92752ff --- /dev/null +++ b/_sass/_mixins.scss @@ -0,0 +1,10 @@ +/** + * Clearfix + */ +@mixin clearfix() { + &:after { + content: ""; + display: table; + clear: both; + } +} diff --git a/_sass/_pages.scss b/_sass/_pages.scss new file mode 100644 index 0000000..7408130 --- /dev/null +++ b/_sass/_pages.scss @@ -0,0 +1,2 @@ +article.page { +} diff --git a/_sass/_posts.scss b/_sass/_posts.scss new file mode 100644 index 0000000..72ea481 --- /dev/null +++ b/_sass/_posts.scss @@ -0,0 +1,46 @@ +article.post { +} + +#share { + margin: 50px 0; + + h2 { + font-size: 1.2em; + } + + ul { + list-style: none; + margin: 0; + + li { + display: inline; + + a { + &:hover { + text-decoration: none; + } + + svg { + width: 50px; + height: 50px; + fill: $share-icon-fill-color; + } + } + } + } +} + +nav.post-nav { + @include clearfix(); + + a { + display: block; + margin-bottom: $spacing-unit; + float: left; + max-width: 48%; + + &.next { + float: right; + } + } +} \ No newline at end of file diff --git a/_sass/_syntax-highlighting.scss b/_sass/_syntax-highlighting.scss new file mode 100644 index 0000000..1311c9b --- /dev/null +++ b/_sass/_syntax-highlighting.scss @@ -0,0 +1,100 @@ +/* Solarized Dark + +For use with Jekyll and Pygments + +http://ethanschoonover.com/solarized + +SOLARIZED HEX ROLE +--------- -------- ------------------------------------------ +base03 #002b36 background +base02 #073642 background highlights +base01 #586e75 comments / secondary content +base1 #93a1a1 body text / default code / primary content +orange #cb4b16 constants +red #dc322f regex, special keywords +blue #268bd2 reserved keywords +cyan #2aa198 strings, numbers +green #859900 operators, other keywords +*/ + +.gist .highlight, .gist .blob-code-inner { font-size: inherit !important } +.gist .highlight { line-height: 1.25em !important } +.gist .gist-meta { background-color: #073642 !important; color: #93a1a1 !important } +.gist .gist-meta a { color: #268bd2 !important } +.gist .gist-data, .gist .highlight { background-color: #002b36 !important; color: #93a1a1 !important } +.gist .gist-data, .gist .gist-file { border-color: #073642 !important } +.gist .pl-smi, .gist .pl-s .pl-s1, .gist .blob-code-inner, .gist .blob-num { color: #93a1a1 !important } +.gist .pl-k { color: #268bd2 !important } +.gist .pl-e, .gist .pl-en { color: #cb4b16 !important } +.gist .pl-s, .gist .pl-pds, .gist .pl-s .pl-pse .pl-s1, .gist .pl-sr, +.gist .pl-sr .pl-cce, .gist .pl-sr .pl-sre, .gist .pl-sr .pl-sra { color: #2aa198 !important } + +.highlight { background-color: #002b36; color: #93a1a1 } +.highlight .c { color: #586e75 } /* Comment */ +.highlight .err { color: #93a1a1 } /* Error */ +.highlight .g { color: #93a1a1 } /* Generic */ +.highlight .k { color: #859900 } /* Keyword */ +.highlight .l { color: #93a1a1 } /* Literal */ +.highlight .n { color: #93a1a1 } /* Name */ +.highlight .o { color: #859900 } /* Operator */ +.highlight .x { color: #cb4b16 } /* Other */ +.highlight .p { color: #93a1a1 } /* Punctuation */ +.highlight .cm { color: #586e75 } /* Comment.Multiline */ +.highlight .cp { color: #859900 } /* Comment.Preproc */ +.highlight .c1 { color: #586e75 } /* Comment.Single */ +.highlight .cs { color: #859900 } /* Comment.Special */ +.highlight .gd { color: #2aa198 } /* Generic.Deleted */ +.highlight .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #dc322f } /* Generic.Error */ +.highlight .gh { color: #cb4b16 } /* Generic.Heading */ +.highlight .gi { color: #859900 } /* Generic.Inserted */ +.highlight .go { color: #93a1a1 } /* Generic.Output */ +.highlight .gp { color: #93a1a1 } /* Generic.Prompt */ +.highlight .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #cb4b16 } /* Generic.Subheading */ +.highlight .gt { color: #93a1a1 } /* Generic.Traceback */ +.highlight .kc { color: #cb4b16 } /* Keyword.Constant */ +.highlight .kd { color: #268bd2 } /* Keyword.Declaration */ +.highlight .kn { color: #859900 } /* Keyword.Namespace */ +.highlight .kp { color: #859900 } /* Keyword.Pseudo */ +.highlight .kr { color: #268bd2 } /* Keyword.Reserved */ +.highlight .kt { color: #dc322f } /* Keyword.Type */ +.highlight .ld { color: #93a1a1 } /* Literal.Date */ +.highlight .m { color: #2aa198 } /* Literal.Number */ +.highlight .s { color: #2aa198 } /* Literal.String */ +.highlight .na { color: #93a1a1 } /* Name.Attribute */ +.highlight .nb { color: #B58900 } /* Name.Builtin */ +.highlight .nc { color: #268bd2 } /* Name.Class */ +.highlight .no { color: #cb4b16 } /* Name.Constant */ +.highlight .nd { color: #268bd2 } /* Name.Decorator */ +.highlight .ni { color: #cb4b16 } /* Name.Entity */ +.highlight .ne { color: #cb4b16 } /* Name.Exception */ +.highlight .nf { color: #268bd2 } /* Name.Function */ +.highlight .nl { color: #93a1a1 } /* Name.Label */ +.highlight .nn { color: #93a1a1 } /* Name.Namespace */ +.highlight .nx { color: #93a1a1 } /* Name.Other */ +.highlight .py { color: #93a1a1 } /* Name.Property */ +.highlight .nt { color: #268bd2 } /* Name.Tag */ +.highlight .nv { color: #268bd2 } /* Name.Variable */ +.highlight .ow { color: #859900 } /* Operator.Word */ +.highlight .w { color: #93a1a1 } /* Text.Whitespace */ +.highlight .mf { color: #2aa198 } /* Literal.Number.Float */ +.highlight .mh { color: #2aa198 } /* Literal.Number.Hex */ +.highlight .mi { color: #2aa198 } /* Literal.Number.Integer */ +.highlight .mo { color: #2aa198 } /* Literal.Number.Oct */ +.highlight .sb { color: #586e75 } /* Literal.String.Backtick */ +.highlight .sc { color: #2aa198 } /* Literal.String.Char */ +.highlight .sd { color: #93a1a1 } /* Literal.String.Doc */ +.highlight .s2 { color: #2aa198 } /* Literal.String.Double */ +.highlight .se { color: #cb4b16 } /* Literal.String.Escape */ +.highlight .sh { color: #93a1a1 } /* Literal.String.Heredoc */ +.highlight .si { color: #2aa198 } /* Literal.String.Interpol */ +.highlight .sx { color: #2aa198 } /* Literal.String.Other */ +.highlight .sr { color: #dc322f } /* Literal.String.Regex */ +.highlight .s1 { color: #2aa198 } /* Literal.String.Single */ +.highlight .ss { color: #2aa198 } /* Literal.String.Symbol */ +.highlight .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #268bd2 } /* Name.Variable.Class */ +.highlight .vg { color: #268bd2 } /* Name.Variable.Global */ +.highlight .vi { color: #268bd2 } /* Name.Variable.Instance */ +.highlight .il { color: #2aa198 } /* Literal.Number.Integer.Long */ diff --git a/about.md b/about.md new file mode 100644 index 0000000..ff0bee7 --- /dev/null +++ b/about.md @@ -0,0 +1,24 @@ +--- +layout: page +title: About me +--- +I am a young programmer from Groningen, The Netherlands. I care deeply about +learning new things and enjoy passing that knowledge on in a form that enables +others to use it. I love making easy-to-use tools that support people in +creating great things, and help them to write and tell the stories of their own +lives. + +Our world is changing ever more quickly. Interaction between people and machines +is becoming more common. Devices need to become better at interacting with human +beings, rather than the other way around. And if they are to interact with us +naturally, machines must learn to understand the world around us. Our interaction +with the digital world will one day overlap seamlessly with our interaction with +the physical world, of that I have no doubt. We've come so very far in the last +few decades, and we have so much further to go still. + +--- + +Besides this site, you can also follow me on [Twitter](https://twitter.com/RomkeVdMeulen) +and read my (more philosophical) articles on [Medium](). You can also +[contact me directly]({{ site.baseurl }}/contact). Potential employers can take a +look at [my resumé]({{ site.baseurl }}/resume). diff --git a/contact.html b/contact.html new file mode 100644 index 0000000..9abf053 --- /dev/null +++ b/contact.html @@ -0,0 +1,5 @@ +--- +layout: base +title: Contact +--- + diff --git a/css/main.scss b/css/main.scss new file mode 100644 index 0000000..465152f --- /dev/null +++ b/css/main.scss @@ -0,0 +1,73 @@ +--- +# Only the main Sass file needs front matter (the dashes are enough) +--- +@charset "utf-8"; +@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,300); + + +// Our variables +$base-font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$base-font-size: 16px; +$base-font-weight: 400; +$small-font-size: $base-font-size * 0.875; +$base-line-height: 1.5; + +$spacing-unit: 30px; + +$text-color: #bbb; +$background-color: #222; + +$icon-fill-color: $text-color; +$menu-icon-fill-color: darken($text-color, 20%); +$share-icon-fill-color: #eee; + +$link-color: #268bd2; +$link-visited-color: lighten($link-color, 20%); +$link-hover-color: #fff; + +$nav-border-color: #ccc; +$nav-background-color: $background-color; + +$post-meta-color: #ccc; + +$divider-color: #ccc; +$blockquote-color: #ccc; +$blockquote-border-color: #ccc; + +$code-border-color: #073642; +$code-background-color: #002b36; +$code-text-color: #839496; + +$content-width: 860px; + +$on-palm: 600px; +$on-laptop: 800px; + +$z-index-overlay: 1; +$z-index-wrapper: 100; + +// Use media queries like this: +// @include media-query($on-palm) { +// .wrapper { +// padding-right: $spacing-unit / 2; +// padding-left: $spacing-unit / 2; +// } +// } +@mixin media-query($device) { + @media screen and (max-width: $device) { + @content; + } +} + + + +// Import partials from `sass_dir` (defaults to `_sass`) +@import + "mixins", + "base", + "layout", + "syntax-highlighting", + "pages", + "posts", + "index" +; diff --git a/feed.xml b/feed.xml new file mode 100644 index 0000000..a6628bd --- /dev/null +++ b/feed.xml @@ -0,0 +1,30 @@ +--- +layout: null +--- + + + + {{ site.title | xml_escape }} + {{ site.description | xml_escape }} + {{ site.url }}{{ site.baseurl }}/ + + {{ site.time | date_to_rfc822 }} + {{ site.time | date_to_rfc822 }} + Jekyll v{{ jekyll.version }} + {% for post in site.posts limit:10 %} + + {{ post.title | xml_escape }} + {{ post.content | xml_escape }} + {{ post.date | date_to_rfc822 }} + {{ post.url | prepend: site.baseurl | prepend: site.url }} + {{ post.url | prepend: site.baseurl | prepend: site.url }} + {% for tag in post.tags %} + {{ tag | xml_escape }} + {% endfor %} + {% for cat in post.categories %} + {{ cat | xml_escape }} + {% endfor %} + + {% endfor %} + + diff --git a/img/bg.jpg b/img/bg.jpg new file mode 100644 index 0000000..a9405e0 Binary files /dev/null and b/img/bg.jpg differ diff --git a/img/books.jpg b/img/books.jpg new file mode 100644 index 0000000..f87327f Binary files /dev/null and b/img/books.jpg differ diff --git a/img/favicon.png b/img/favicon.png new file mode 100644 index 0000000..b24a572 Binary files /dev/null and b/img/favicon.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..1aaf6df --- /dev/null +++ b/index.html @@ -0,0 +1,45 @@ +--- +layout: page +--- +
+ {% for post in paginator.posts %} +
+
+

{{ post.title }}

+ + {% include icons/clock-o.svg %} + + + {% if post.tags.size > 0 %} + + {% include icons/tag.svg %} + {{ post.tags | array_to_sentence_string }} + + {% endif %} +
+ +
{{ post.excerpt }}
+
+ {% endfor %} + +
+ {% if paginator.next_page %} + + « Older Posts + {% endif %} + + {% if paginator.previous_page %} + {% if paginator.previous_page == 1 %} + + + Newer Posts » + + {% else %} + + + Newer Posts » + + {% endif %} + {% endif %} +
+
\ No newline at end of file diff --git a/resume.html b/resume.html new file mode 100644 index 0000000..99a0e38 --- /dev/null +++ b/resume.html @@ -0,0 +1,42 @@ +--- +layout: page +title: Resumé +--- +

Work Experience

+

I support and expand the backend systems, including user and support portals. I work with JSF, JPA, PostgreSQL and the Glassfish application server.

+ +

I deployed ERP software on Windows Server. I also developed a custom web-application using ASP.NET that integrated with this ERP software. This application facilitated manual scheduling of manufacturing based on numerous parameters specific to the customer.

+ +

I implemented websites backends on the Kirra platform, building custom functionality when requirements differed from the platform standards. I also contributed to the automation of the development pipeline by building custom toolsuites for creating and maintaining Kirra websites, locally and remotely over SSH. I mainly used PHP, MySQL and Bash, but also did some frontend work with CSS and jQuery.

+ +

During my university studies, I worked part-time as a PHP programmer. I maintained and extended the company intranet, using PHP and MySQL on the backend, HTML, CSS and Javascript on the frontend. I managed the project by myself for a while, directing two colleagues who were also working on the project. I also designed and implemented a CRM application from scratch, using SennaJS for the frontend.

+ +

Education

+

This course was organized for young professionals with high potential. I was trained in Java SE7 and Java EE7, as well as various methodologies of software design and implementation. At the end of the course I successfully completed the exam for Oracle Certified Associate Java SE7 programmer (1Z0-803).

+ +

I was most interested in cognitive ergonomics: designing products so that their use is natural and easy to understand. I also did a variety of elective courses in philosophy, as well as a fascinating introduction to Computer Graphics and a very thorough introduction to C++ programming. For my final thesis, I connected a Microsoft Kinect camera to an existing CAVE virtual environment to create an interactive virtual interface. You can read more about it here.

+ +

From 2006 to 2008 I was a member of the student faction of the AI department's education advisory committee: we evaluated and suggested improvements to the education programme. I was also an active member of Cover, the student association for the AI and Computer Science departments. I was secretary and later chief of the activities committee, and I became chief editor for the association's magazine "Brainstorm".

+ +

Skills

+ +

Languages

+

I've worked extensively with PHP, SQL and Bash. I've used the Zend PHP Framework. I've also worked with Java SE8 and Java EE7, including JSP, JSF, JAX-RS, JPA and CDI. I've worked with Glassfish, Websphere, Tomcat and JBoss (WildFly) application servers. I've worked with C++, Ruby on Rails, ASP.NET and a little work with Prolog.

+ +

Web

+

I've worked extensively with basic HTML including some HTML5, as well as extensive work with CSS (LESS, SCSS), JavaScript and jQuery. I've done some work with AngularJS. I've worked extensively with XML, JSON, SOAP and REST.

+ +

Database

+

I've worked extensively with MySQL, including administration. I've also worked with T-SQL and PostgreSQL. I've built applications that used MongoDB, but haven't used that database directly.

+ +

Server

+

I've deployed applications with Apache, NginxDocker and Dokku. I'm very familiar with Linux servers.

+ +

Testing

+

I've worked with PHPUnit, JUnit and RSpec. I've used Test-Driven Design as well as Behaviour Driven Design using the Cucumber framework. I've set up and used Jenkins (Hudson) CI and Gitlab CI.

+ +

SCM

+

I've extensively used Git and Subversion (SVN) and am familiar with Bazaar and Mercurial.

+ +

Platforms

+

I've worked extensively with Linux, both desktop and server. I've also worked with every major desktop edition of Windows and with Windows Server 2008 and 2012. I've used OSX, though not recently. I'm very familiar with Bash and ZSH and with Netbeans and Eclipse.