<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Sam's Blog]]></title><description><![CDATA[Lover of all things clever and technical, former YSTV-er doing cloud media stuff at BBC R&D.]]></description><link>https://blog.samn.co.uk</link><generator>RSS for Node</generator><lastBuildDate>Fri, 11 Sep 2026 01:08:51 GMT</lastBuildDate><atom:link href="https://blog.samn.co.uk/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Netboot with Proxy DHCP]]></title><description><![CDATA[samdbmg/dhcp-netboot.xyz is a Docker container that can be dropped into your network to netbook various OS installers and tools. It lets your existing DHCP server hand out addresses, and then acts as a PXE Proxy for clients that want to use netboot.
...]]></description><link>https://blog.samn.co.uk/netboot-with-proxy-dhcp</link><guid isPermaLink="true">https://blog.samn.co.uk/netboot-with-proxy-dhcp</guid><category><![CDATA[sysadmin]]></category><category><![CDATA[Linux]]></category><category><![CDATA[networking]]></category><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Thu, 12 Sep 2024 00:10:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726099492513/087f72e8-087b-4e7d-afd7-500d64df01ae.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://github.com/samdbmg/dhcp-netboot.xyz">samdbmg/dhcp-netboot.xyz</a> is a Docker container that can be dropped into your network to netbook various OS installers and tools. It lets your existing DHCP server hand out addresses, and then acts as a <a target="_blank" href="https://www.ibm.com/docs/en/aix/7.1?topic=protocol-preboot-execution-environment-proxy-dhcp-daemon">PXE Proxy</a> for clients that want to use netboot.</p>
<p>I made it because I can never find a serviceable USB stick or the right assortment of .iso files when I want to do an OS install. But I'd rather not have to completely replace my DHCP server (e.g. my home router) just to netboot one system.</p>
<p>It's built on <a target="_blank" href="https://netboot.xyz/">netboot.xyz</a> which provides a bootable environment with a huge assortment of Linux live CDs, installers, utilities like memtest86 and GParted and even support for the Windows installer (if you bring your own Windows ISO and mount it on your network).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674262808643/20ccdd27-8dcb-4c15-99b6-fed2b6bb3733.png" alt class="image--center mx-auto" /></p>
<p>Connecting the container is a little tricky: standard Docker networking <a target="_blank" href="https://docs.docker.com/network/bridge/">creates a network for containers</a> and then routes traffic between that and the host's network interface using iptables rules, allowing for example traffic to port 8443 on the host to be sent to port 443 on the container. That doesn't work very well with DHCP, which relies on the ability to make and receive broadcasts. All of the participants have to be on the same LAN segment, which means port forwarding isn't going to work. One easy solution is to use Docker's <code>host</code> network mode which makes the container behave as if it were a process on the host from a networking perspective; although that doesn't work if anything else wants to listen on the ports the container uses (which includes port 80 used by netboot.xyz). Another approach demonstrated using a <a target="_blank" href="https://github.com/samdbmg/dhcp-netboot.xyz/blob/main/docker-compose.yml">docker-compose file in the repo</a> uses the <a target="_blank" href="https://docs.docker.com/network/ipvlan/">ipvlan network driver</a> to effectively create a new network interface connected to the same network as the host, in the same IP range as if it were a separate device (akin to how VMs can be connected directly to the host network), which avoids theses issues.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=P-uuXoFdF54&amp;">https://www.youtube.com/watch?v=P-uuXoFdF54&amp;</a></div>
<p> </p>
<p>I've used this to install various new systems at home, to boot into tools when I need to do partition maintenance or test some memory, and also when I want to build a new VM on my workstation (although note that if you're using Hyper-V Generation 2, Secure Boot has to be off for it to work)</p>
]]></content:encoded></item><item><title><![CDATA[Implementing SSO with Traefik]]></title><description><![CDATA[samdbmg/ansible-traefik-auth-proxy combines the Traefik reverse proxy with thomseddon/traefik-forward-auth to provide HTTP reverse proxying, certificate handling with LetsEncrypt and SSO login, either as an Ansible role or a Docker Compose project.
T...]]></description><link>https://blog.samn.co.uk/implementing-sso-with-traefik</link><guid isPermaLink="true">https://blog.samn.co.uk/implementing-sso-with-traefik</guid><category><![CDATA[Traefik]]></category><category><![CDATA[Reverse Proxy]]></category><category><![CDATA[OIDC]]></category><category><![CDATA[Homelab]]></category><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Thu, 12 Sep 2024 00:09:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673883571709/0b671595-d4e2-424f-b483-b87bf0be5780.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://github.com/samdbmg/ansible-traefik-auth-proxy">samdbmg/ansible-traefik-auth-proxy</a> combines the <a target="_blank" href="https://traefik.io/traefik/">Traefik</a> reverse proxy with <a target="_blank" href="https://github.com/thomseddon/traefik-forward-auth">thomseddon/traefik-forward-auth</a> to provide HTTP reverse proxying, certificate handling with LetsEncrypt and SSO login, either as an Ansible role or a Docker Compose project.</p>
<p>The idea is to be able to have a subdomain for each of my servers (<em>e.g.</em> <code>sofaserver.samn.co.uk</code>) and to deploy various self-hosted HTTP services as Docker containers under subdomains (<em>e.g.</em> <code>coolapp.sofaserver.samn.co.uk</code>) which gets exposed with login and certificates automatically sorted out, just by applying labels to the containers when they're launched. Setting that up requires knitting together some bits of configuration, which is why I've built an Ansible role for it.</p>
<h2 id="heading-how-it-works">How it works</h2>
<p>To make configuration based on Docker container labels work, Traefik has the concept of <a target="_blank" href="https://doc.traefik.io/traefik/getting-started/configuration-overview/#the-dynamic-configuration">dynamic configuration</a>. Various providers, such as the Docker Provider supply it with the details of what should be configured at runtime and Traefik listens for containers launching with <code>traefik.*</code> labels attached to expose them accordingly. However, granting anything direct access to Docker via the Docker socket is a security risk: anything with access to that socket is effectively root on the host. Instead, an instance of <a target="_blank" href="https://github.com/Tecnativa/docker-socket-proxy">tecnativa/docker-socket-proxy</a> is deployed which provides read-only access to details about Docker containers but rejects any other requests to reduce the attack surface.</p>
<p>The first part of exposing a service is automatically issuing a TLS certificate with LetsEncrypt for the relevant subdomain. LetsEncrypt has to check that you control the domain for which you've requested a certificate by using one of a series of <a target="_blank" href="https://letsencrypt.org/docs/challenge-types/">challenges</a> such as <code>HTTP-01</code> and <code>DNS-01</code>. In the former LetsEncrypt provides a file to host at a specific location on your web server and verifies it can be accessed over unencrypted HTTP (proving you have control of the location the domain points to), and in the latter, it provides details of a specific DNS TXT record to create (proving you control the domain itself). Fortunately, Traefik takes care of both challenges, either intercepting and responding to requests for the challenge file over HTTP, or creating DNS records on your DNS provider using the <a target="_blank" href="https://github.com/go-acme/lego">go-acme/lego</a> client. In my case, I tend to use <code>DNS-01</code> to avoid having to expose the HTTP port and because some of my services only accept requests from inside my network, but it makes little difference.</p>
<p>The second major piece of automatic configuration is authentication and authorization, to restrict access to my services only to people who should be able to log in. Traefik has support for middleware to change how requests get processed, such as the <a target="_blank" href="https://doc.traefik.io/traefik/middlewares/http/forwardauth/">ForwardAuth middleware</a> which calls some other service and only permits requests when the other service responds that they are permitted. <a target="_blank" href="https://github.com/thomseddon/traefik-forward-auth">thomseddon/traefik-forward-auth</a> is an example of one of those services, and it permits requests that pass an OAuth2 <a target="_blank" href="https://developer.okta.com/blog/2018/04/10/oauth-authorization-code-grant-type">Authorization Code Grant</a>. From a user perspective that looks like what happens when you push a "Sign in with Google" or "Log in with GitHub" button: you're sent to Google/GitHub/<em>et al</em> (the Identity Provider, or IdP), you log in, the Identity Provider sends you back with a code and Traefik uses that to get an Access Token and some details about you (your email address) which it forwards on to the service you're logging in to as the <code>X-Forwarded-User</code> header.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674263965697/dcf26045-a2e1-431a-adf3-3c99b796049d.png" alt class="image--center mx-auto" /></p>
<p>Let's think about what happens in our previous example of <code>coolapp.sofaserver.samn.co.uk</code>, and if Google was the IdP in use. When you browse to that site you'd be redirected to Google to log in, with a callback URL pointing to <code>auth.sofaserver.samn.co.uk</code>: the address of the Forward Auth middleware. After logging in successfully you are redirected to that callback URL with a code, and the middleware makes a request to Google behind the scenes for an access token and your email address. Finally, the middleware sets a cookie to keep you logged in and responds to Traefik that auth succeeded and the request should be allowed and the <code>X-Forwarded-User</code> header should be passed to the backing service.</p>
<h2 id="heading-docker-compose-project">Docker Compose Project</h2>
<p>I mentioned at the beginning that the repository contained both an Ansible Role and a Docker Compose project. Ansible is ideal for automating complex infrastructure and maintaining a long-term deployment, but a lot of work for a one-off demo, which is why I built a Docker version. It starts up the same set of containers (Traefik, the auth middleware and the Docker socket proxy) as when deployed using Ansible, but also includes a container that generates all the config files. In principle, you can copy the <code>docker-compose.yml</code> file from the repository, set environment variables as needed (e.g. for access to your Identity Provider) and then run <code>docker-compose run config-generator &amp;&amp; docker-compose up reverse-proxy -d</code> and have a working Traefik/LetsEncrypt/SSO setup.</p>
<p>The <code>config-generator</code> container runs a one-off <code>ansible-playbook</code> command to generate config files, using the same Ansible role, with a set of flags to disable starting the other containers.</p>
<h2 id="heading-unit-testing-amp-ci">Unit Testing &amp; CI</h2>
<p>The role has a set of automated tests and linting configured using <a target="_blank" href="https://molecule.readthedocs.io/en/latest/">Ansible Molecule</a> to verify that it works as expected. Molecule provides a way to start a Docker container and orchestrate Ansible to connect to that container, apply the role, check that it's idempotent and run a verification process against the container afterward.</p>
<p>Verifying the reverse proxy role works is complicated, because the tests have to work in GitHub Actions without human interaction, but also exercise enough of the certificate issuance and auth processes to prove they work.</p>
<p>Testing certificates in a CI environment implies the <code>DNS-01</code> challenge, because it's not practical (nor wise!) to expect your CI system to be accessible from the Internet. Instead I've delegated a subdomain to another provider (<a target="_blank" href="https://desec.io">desec.io</a> and GitHub Actions has a token to access the deSEC API. When the test runs, it generates a random string and prepends it to the delegated domain to produce the test domain (<em>e.g.</em> <code>abcd1234.ci-dns.samn.co.uk</code>), which is saved to a file and used to issue certificates (initially it just used the delegated domain directly, but that led to confusing test failures when they ran in parallel!)</p>
<p>For testing the ForwardAuth middleware I've used <a target="_blank" href="https://github.com/navikt/mock-oauth2-server">navikt/mock-oauth2-server</a>. It behaves like an IdP that you're already logged in to, so then it's just a case of testing the various redirects to and from the IdP appear, and following them.</p>
<h2 id="heading-further-reading">Further Reading</h2>
<ul>
<li><p>The repository can be found at <a target="_blank" href="https://github.com/samdbmg/ansible-traefik-auth-proxy">https://github.com/samdbmg/ansible-traefik-auth-proxy</a></p>
</li>
<li><p>Or the role is on Ansible Galaxy at <a target="_blank" href="https://galaxy.ansible.com/samdbmg/traefik_auth_proxy">https://galaxy.ansible.com/samdbmg/traefik_auth_proxy</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Using Ansible for my self-hosted apps]]></title><description><![CDATA[I self-host a variety of apps on my own infrastructure, and make extensive use of Ansible playbooks to keep it all running, document everything as code and make it easy to run up new environments to test changes without breaking everything! This post...]]></description><link>https://blog.samn.co.uk/using-ansible-for-my-self-hosted-apps</link><guid isPermaLink="true">https://blog.samn.co.uk/using-ansible-for-my-self-hosted-apps</guid><category><![CDATA[SelfHosting]]></category><category><![CDATA[ansible]]></category><category><![CDATA[Homelab]]></category><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Thu, 12 Sep 2024 00:08:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1674264904763/3c904ed7-4b30-493d-8745-8c5d55f07032.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I self-host a variety of apps on my own infrastructure, and make extensive use of <a target="_blank" href="https://www.ansible.com/">Ansible</a> playbooks to keep it all running, document everything as code and make it easy to run up new environments to test changes without breaking everything! This post summarises how the playbooks work, how I do testing, and how I try to help myself pick up context and dive in quickly when I have a few spare minutes.</p>
<h1 id="heading-why-what-and-where"><strong>Why What and Where</strong></h1>
<p>Some things I self-host for practical reasons, like the much better transfer rates (and cheaper per GB storage cost) I get from my home WiFi to the server under my sofa compared to the cloud. Sometimes it's for principled reasons, like wanting to retain control over where my data lives and who has access to it. And sometimes it's just because I like doing it!</p>
<p>Amongst other things I'm running:</p>
<ul>
<li><p><a target="_blank" href="https://nextcloud.com/">Nextcloud</a> for files and notes</p>
</li>
<li><p><a target="_blank" href="https://www.home-assistant.io/">Home Assistant</a> to monitor and control various sensors and smart devices (plus ESPHome to manage device firmware)</p>
</li>
<li><p><a target="_blank" href="https://www.timescale.com/">TimescaleDB</a> and <a target="_blank" href="https://grafana.com/">Grafana</a> for storage and more powerful analysis of those sensor data</p>
</li>
<li><p><a target="_blank" href="https://convos.chat/">Convos</a> IRC client for always-on IRC (I used to use <a target="_blank" href="https://thelounge.chat/">The Lounge</a> but Convos works better with SSO)</p>
</li>
<li><p><a target="_blank" href="https://www.firefly-iii.org/">Firefly III</a> for tracking my finances</p>
</li>
<li><p>Exim for outgoing email (incoming is handled by <a target="_blank" href="https://www.cloudflare.com/products/email-routing/">Cloudflare Email Routing</a> so I don't have to worry about losing mail if my servers go down)</p>
</li>
</ul>
<p>These are spread across a mixture of an old laptop running under my sofa with some storage attached (called <code>sofaserver</code>), and a <a target="_blank" href="https://www.vultr.com/">Vultr</a> VPS in London (called <code>gate</code>).</p>
<h1 id="heading-how-it-works">How it works</h1>
<p>Most of the apps are deployed using Docker and orchestrated with <code>docker-compose</code>. Compose lets you write YAML files describing a set of one or more containers that make up an app, define some networks to connect those containers, and manage that app as a single unit. I've considered using a more "serious" orchestrator like Kubernetes in the past, but Compose is very easy to use, and Kubernetes is a lot of complexity for only two hosts in my spare time! Below is an example of a Compose file that will be rendered through Ansible templates.</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">grafana:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">grafana/grafana-oss:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">grafana</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/opt/grafana/data/:/var/lib/grafana:rw</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/opt/grafana/grafana.ini:/etc/grafana/grafana.ini:ro</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ container_user.uid }}</span>:<span class="hljs-template-variable">{{ container_user.gid }}</span>"</span>
    <span class="hljs-attr">labels:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.enable=true</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.grafana.rule=Host(`grafana.{{</span> <span class="hljs-string">services_domain</span> <span class="hljs-string">}}`)</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.services.grafana.loadbalancer.server.port=3000</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.grafana.entrypoints=websecure</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.grafana.tls.certresolver=default</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.grafana.middlewares=traefik-forward-auth</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">traefik:</span>
    <span class="hljs-attr">external:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>Notice the <code>traefik...</code> labels? They configure the <a target="_blank" href="https://traefik.io/traefik/">Traefik</a> proxy used to expose the container externally. Traefik reads the request (which is made to the standard HTTPS port 443 regardless of destination), presents the appropriate TLS certificate and routes the request to the backend. It also automatically issues and renews certificates using LetsEncrypt. I use the <a target="_blank" href="https://letsencrypt.org/docs/challenge-types/#dns-01-challenge">DNS-01</a> challenge via my Cloudflare DNS, which also means I can restrict some apps to only allow connections from my home network, while still using publicly trusted certificates.</p>
<p>A Traefik middleware, <a target="_blank" href="https://github.com/thomseddon/traefik-forward-auth">traefik-forward-auth</a> handles authorisation to access my apps. It intercepts requests to perform a standard OIDC flow, only permitting those which log in successfully. The authentication service itself is not self-hosted; it would be possible to use something like Keycloak for this, but I've gone with a managed service instead to make securing the login process "somebody else's problem".</p>
<p>A previous iteration of this infrastructure used NGINX to configure the reverse proxying, and I built an Ansible role, <a target="_blank" href="https://github.com/samdbmg/ansible-nginx-gateway">nginx-gateway</a> to set that up. However it didn't take care of issuing certificates, there was some Terraform that had to be rerun every few months, which is a strength of Traefik. That iteration also used client certificates (aka mTLS) for authentication, which was very secure but clunky: certs were issued with cobbled together <code>openssl</code> commands and several mobile apps and integrations wouldn't work because they couldn't present a client certificate, which is why I switched to OIDC.</p>
<h1 id="heading-ansible-playbooks">Ansible Playbooks</h1>
<p>The Ansible playbooks that manage all of this are stored in a private GitHub repository. The repo is laid out broadly along the lines of the layout <a target="_blank" href="https://docs.ansible.com/ansible/2.9/user_guide/playbooks_best_practices.html#directory-layout">in the Ansible documentation</a>: there's a main playbook that imports various other playbooks to set everything up, along with host and group vars and an inventory file per environment.</p>
<pre><code class="lang-plaintext">site-automation/
  group_vars/
    production/
      cleartext.yml  # Non-sensitive vars for the production environmnet
      secret.yml  # Vault file containing encrypted secrets for production environment
    staging/
      cleartext.yml
      secret.yml
    vagrant_local/
      cleartext.yml
      secret.yml
  host_vars/
    gate/
      become.yml  # Password to become root on system
      gate.yml  # Vars that only apply to gate
    sofaserver/
      become.yml
      sofaserver.yml
  notes/
    setup-grafana.md  # Various Markdown notes about steps that have to be done manually
    setup-login.md
    ...
  templates/
    compose/
      esphome.yml  # Docker Compose file template for ESP Home
      firefly.yml
      grafana.yml
      ...
    gate/
      exim-cert-extract-cron.j2  # Exim certificate extraction cronjob template
      exim-cert-reload.sh  # Cert extraction script
      ...
    sofaserver/
      firefly-config.env.j2
      hdparm.conf.j2
      ...
  vagrant-testing/
    Vagrantfile  # Vagrantfile for describing local dev environment
    provision.sh  # Helper script to call `ansible-playbook` using Vagrant inventory
  .gitignore
  ansible.cfg
  inventory-production
  inventory-staging
  requirements.yml  # Roles used by the repo
  servers-00-all.yml  # Site playbook importing the others
  servers-15-networks.yml  # Configure networking and basic security hardening
  servers-20-gate.yml  # Server-specific plays for gate
  servers-20-sofaserver.yml  # Server-specific plays for sofaserver
  servers-30-containers.yml  # Set up containerised services
  servers-40-gate-post.yml  # Plays to run on gate after containers are up
</code></pre>
<p>The assumed starting point is a basic Ubuntu install, either using the installer (for the machines at home) or one of the cloud images on my VPS provider.</p>
<p>First off the <code>servers-15-networks.yml</code> playbook does some security hardening and network configuration by applying the firewall and <a target="_blank" href="https://github.com/geerlingguy/ansible-role-security">security</a> roles from Jeff Geerling. It also sets up <a target="_blank" href="https://www.wireguard.com/">Wireguard</a> between the machines to have a secure internal network (using <a target="_blank" href="https://github.com/githubixx/ansible-role-wireguard">githubixx.ansible_role_wireguard</a>) and creates records in Cloudflare DNS for the machine's IP addresses.</p>
<p>Next up is host-specific configuration. For <code>sofaserver</code> that means checking the health of the storage and RAID array and mounting it, although creating the array is done manually. It also includes setting up Samba file sharing and cloning a few HomeAssistant addons. For <code>gate</code> the Exim mail config is created, along with <a target="_blank" href="https://blog.cubieserver.de/2021/minimal-traefik-v2-certificate-export/">a script</a> to extract TLS certificates from Traefik to reuse them for Exim.</p>
<p>Setting up and launching containers is the next step in <code>servers-30-containers.yml</code>, starting with installing Docker and deploying the <a target="_blank" href="https://github.com/samdbmg/ansible-traefik-auth-proxy">samdbmg.traefik-auth-proxy</a> role, then setting up each service.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>  
  <span class="hljs-attr">grafana:</span>
    <span class="hljs-attr">compose_file:</span> <span class="hljs-string">templates/compose/grafana.yml</span>
    <span class="hljs-attr">data_backups:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">/opt/grafana/data</span>
    <span class="hljs-attr">templates:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">src:</span> <span class="hljs-string">templates/sofaserver/grafana.ini</span>
        <span class="hljs-attr">dest:</span> <span class="hljs-string">/opt/grafana/grafana.ini</span>
    <span class="hljs-attr">dns_entry:</span>
      <span class="hljs-attr">record:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ ('grafana' + services_zone) | trim('.') }}</span>"</span>
</code></pre>
<p>Each container service has a host var entry like the one above, which is used by the <code>servers-30-containers.yml</code> playbook to set it up. Compose files are templated to the host along with any config file templates, and data directories are created for containers to mount. Where those data directories contain useful application state, <a target="_blank" href="https://duplicity.gitlab.io/">Duplicity</a> is used for periodic backups of the data (using <a target="_blank" href="https://github.com/samdbmg/ansible-schedule-duplicity">samdbmg.schedule-duplicity</a>).</p>
<p>Finally, any extra post-container config is done, e.g. in <code>servers-40-gate-post.yml</code> which ensures the Traefik -&gt; Exim certificate extraction process has run at least once.</p>
<p>Each deployed environment (production, staging and local testing) has its own set of <code>group_vars</code>, used to define the domain under which everything is hosted, versions of services, IP address ranges, etc.</p>
<h1 id="heading-testing-amp-development">Testing &amp; Development</h1>
<p>Since this is something I tinker with in my spare time (and that's spare time from both work and parenting) I have to pick it back up and be productive quite quickly. To help with that I try to keep detailed notes of what I'm working on: my current approach is a GitHub Issue for each bug/feature, into which I summarise progress, write up my research and add links to the commits as I work. I also maintain throwaway test environments to thoroughly test changes so I'm unlikely to break something and find I have to fix it immediately when I'm supposed to be on baby duty!</p>
<p>One of those test environments is a Vagrant configuration I maintain for each host, which automates building reproducible, disposable infrastructure.</p>
<p>Vagrant sets up a VM for each host, connects it to an internal network, and also applies any required hardware customisation like attaching extra disks. The Vagrant <a target="_blank" href="https://developer.hashicorp.com/vagrant/docs/provisioning/ansible">Ansible provisioner</a> sorts out creating an inventory file, and a little script like the following lets me run Ansible commands using that inventory file (e.g. to apply part of the playbook for faster iterations than running <code>vagrant provision</code>).</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-comment"># Provision a single Vagrant VM using Ansible - mostly as a shortcut to save some typing. First arg is VM, rest are</span>
<span class="hljs-comment"># passed through</span>
VM_NAME=<span class="hljs-variable">$1</span>
<span class="hljs-built_in">shift</span>

ANSIBLE_CONFIG=../ansible.cfg ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook \
    -i .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory \
    ../servers-00-all.yml \
    --<span class="hljs-built_in">limit</span> <span class="hljs-string">"<span class="hljs-variable">${VM_NAME}</span>"</span> <span class="hljs-variable">$@</span>
</code></pre>
<p>Once a change to the playbooks has been written, I have a "staging" environment: VMs on a workstation that are very similar to the real production environment. I use this to practice complex upgrades that need to be tested in an environment that closely matches the real thing, like OS upgrades.</p>
<pre><code class="lang-plaintext">ansible-playbook -i inventory-production servers-00-all.yml -v --check --diff 2&gt;&amp;1 | tee check-prod-update.log
</code></pre>
<p>Finally, once a change is ready to go, I run Ansible in <a target="_blank" href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_checkmode.html">check and diff mode</a> using the command above, and review all the changes it's going to make to the real thing, which has saved me a few times!</p>
<h1 id="heading-the-future">The Future</h1>
<p>I'm going to keep evolving this setup and there are quite a few things I'd like to improve upon. I would like to have more CI/CD on some of the testing, perhaps being able to automatically power up the staging environment and apply changes to it. I've also got a couple of bugs to iron out, and a very long list of apps to test out and play with!</p>
]]></content:encoded></item><item><title><![CDATA[How to upgrade TimescaleDB (and PostgreSQL) in a container]]></title><description><![CDATA[I have a TimescaleDB container running on my home server, and recently I wanted to upgrade it from PostgreSQL 13 to PostgreSQL 16. This post documents the process I settled on (and hints at the false starts along the way!)
I have a handful of sensors...]]></description><link>https://blog.samn.co.uk/how-to-upgrade-timescaledb-and-postgresql-in-a-container</link><guid isPermaLink="true">https://blog.samn.co.uk/how-to-upgrade-timescaledb-and-postgresql-in-a-container</guid><category><![CDATA[timescaledb]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Docker]]></category><category><![CDATA[SelfHosting]]></category><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Sat, 07 Sep 2024 22:22:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1725575297848/b2c3fbf2-0dbb-412c-a2d3-cbbc6c1d746f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have a <a target="_blank" href="https://www.timescale.com/">TimescaleDB</a> container running on my home server, and recently I wanted to upgrade it from PostgreSQL 13 to PostgreSQL 16. This post documents the process I settled on (and hints at the false starts along the way!)</p>
<p>I have a handful of sensors around my house, monitoring things like temperature, air quality, broadband speed and latency, etc. Those are all linked back to <a target="_blank" href="https://www.home-assistant.io/">Home Assistant</a>, but by default it only stores 24 hours of data. I use the excellent <a target="_blank" href="https://github.com/freol35241/ltss">LTSS (Long Term State Storage)</a> module to push that data into Timescale DB, and then I can display and query it much further back in Grafana.</p>
<p>I use Docker Compose to deploy the Home Assistant/Grafana/Timescale DB combo, and had it set to use the <code>timescale/timescaledb-ha:pg13</code> docker image - naturally the first thing I tried was just to bump the version to <code>pg16</code> (something <a target="_blank" href="https://docs.timescale.com/self-hosted/latest/upgrades/upgrade-docker/">the manual</a> hints at doing).</p>
<p>This broke my database, in quite a big way! Unsurprisingly, you can't just go up 3 major versions of the database and expect it to work: it fails to start complaining the database is an unsupported version. Fortunately I did this on my "staging environment" (as set of VMs I use to test stuff like this), so I rolled back to a backup and read up on <code>pg_upgrade</code> which got me to this process. Since initially testing this it's been better documented in the <a target="_blank" href="https://docs.timescale.com/self-hosted/latest/upgrades/upgrade-pg/">TimescaleDB docs</a>, but I thought the step by step process was still useful.</p>
<h1 id="heading-the-process">The Process</h1>
<p><em>Update current. Check how far it went (2.5 to 2.10 for me), find newest version. Upgrade to there, and repeat</em></p>
<p>The basic process is to upgrade your existing TimescaleDB extension to the latest version supported by your version of PostgreSQL, then upgrade your database to the latest version that supports that extension version, and repeat the process until you reach the version you want!</p>
<p>To actually upgrade PostgreSQL you start a new, empty database running the new version. Then you install the old version's binaries into it, and use <code>pg_upgrade</code> to move the old DB content into the new DB.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725745584451/122f229f-c3fc-4f33-a091-c204df54042b.png" alt class="image--center mx-auto" /></p>
<p>In theory this table in the docs lets you figure out how many steps you'll need to take: upgrade TimescaleDB to the newest version with a green tick for your PostgreSQL version, then upgrade PostgreSQL to a version that still has a green tick, then upgrade TimescaleDB, and so on. However I had some trouble in that my PostgreSQL 13/TimescaleDB 2.5 install wouldn't go higher than 2.10, so I had to take two steps: to PG15/Timescale2.10 and then to PG16/Timescale2.16.</p>
<p>For the step-by-step instructions, I'm working based on my Docker Compose environment, using a Compose file like the one below.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">timescaledb:</span>
  <span class="hljs-attr">container_name:</span> <span class="hljs-string">timescaledb</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">timescale/timescaledb-ha:pg13-all</span>
  <span class="hljs-attr">environment:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=usr</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=NOTMYPASSWORD</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=ha</span>
  <span class="hljs-attr">volumes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'/opt/homeassistant/timescaledb/pgdata:/home/postgres/pgdata'</span>
  <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
  <span class="hljs-attr">networks:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">default</span>
</code></pre>
<p>Here it is step by step.</p>
<h3 id="heading-step-0-make-a-backup">Step 0: Make a backup!</h3>
<p>Before you start, take a backup of your database unless you <em>really, really</em> don't care about the data. But if that's the case, why bother upgrading?</p>
<h3 id="heading-step-1-upgrade-timescaledb">Step 1: Upgrade TimescaleDB</h3>
<p>First upgrade TimescaleDB. With the server running, run the statements below on all your databases:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">ALTER</span> EXTENSION timescaledb <span class="hljs-keyword">UPDATE</span>;
<span class="hljs-keyword">CREATE</span> EXTENSION <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> timescaledb_toolkit;
<span class="hljs-keyword">ALTER</span> EXTENSION timescaledb_toolkit <span class="hljs-keyword">UPDATE</span>;
</code></pre>
<p>In the Docker Compose environment shown above, you could run something like this to do them all in one go:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> DBNAMES=$(docker compose <span class="hljs-built_in">exec</span> timescaledb psql \
    -U usr -d postgres --no-align --tuples-only \
    -qc <span class="hljs-string">'SELECT datname from pg_database;'</span>)
<span class="hljs-keyword">for</span> DBNAME <span class="hljs-keyword">in</span> <span class="hljs-variable">$DBNAMES</span>; <span class="hljs-keyword">do</span>
    docker compose <span class="hljs-built_in">exec</span> timescaledb psql -U usr -d <span class="hljs-variable">${DBNAME}</span> \
        -qc <span class="hljs-string">'ALTER EXTENSION timescaledb UPDATE; CREATE EXTENSION IF NOT EXISTS timescaledb_toolkit; ALTER EXTENSION timescaledb_toolkit UPDATE;'</span>;
<span class="hljs-keyword">done</span>
</code></pre>
<h3 id="heading-step-2-move-your-existing-data-elsewhere-start-an-empty-database">Step 2: Move your existing data elsewhere, start an empty database</h3>
<p>Next, update your <code>docker-compose.yml</code> to move the database mount somewhere else inside the container, then bump the version. Stop the database for now and don't start it yet.</p>
<p>From the snippet of Compose file above the important modification is to change the volume for the data to be somewhere else inside the container, while also mounting the original location. That way, you can still access the old DB, but the container can start from clean. In the example below, I've moved it to <code>/mnt/olddata</code></p>
<p>I use mounts on the host filesystem for my persistent data, so notice I've changed the host mount path too - I've also run <code>mv /opt/homeassistant/timescaledb/pgdata /opt/homeassistant/oldtimescale</code> in preparation.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">timescaledb:</span>
  <span class="hljs-attr">container_name:</span> <span class="hljs-string">timescaledb</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">timescale/timescaledb-ha:pg15-all</span>
  <span class="hljs-attr">environment:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=usr</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=NOTMYPASSWORD</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=ha</span>
  <span class="hljs-attr">volumes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'/opt/homeassistant/timescaledb/pgdata:/home/postgres/pgdata'</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'/opt/homeassistant/oldtimescale:/mnt/olddata'</span>
  <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
  <span class="hljs-attr">networks:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">default</span>
</code></pre>
<p>Notice I've also bumped the version of the database, ready to start a new, empty one.</p>
<p><strong>Note:</strong> You don't want anything to be <em>writing</em> to the database while you're doing this, so if you have other services that access the database, make sure they aren't running.</p>
<p>Next, create the directory structure for the new database and fix the permissions. Matching the example above:</p>
<pre><code class="lang-bash">sudo mkdir -p /opt/homeassistant/timescaledb/pgdata/data
sudo chown -R 1000 /opt/homeassistant/timescaledb/pgdata
</code></pre>
<h3 id="heading-step-3-install-the-old-version-into-the-container-use-it-to-run-the-upgrade">Step 3: Install the old version into the container, use it to run the upgrade</h3>
<p>Next, get a shell inside the container to install the old version of PostgreSQL. You'll want the new database to be empty for this, so make sure the container hasn't been started since the last step.</p>
<p>Run something like <code>docker compose run --user root --entrypoint /bin/bash timescaledb</code> to start a shell inside the container.</p>
<p>Then run the following commands to install the old PostgreSQL binaries and then run the upgrade command.</p>
<pre><code class="lang-bash">apt update

<span class="hljs-comment"># Update the version as needed (your Docker image may include it already)</span>
apt install postgresql-13

<span class="hljs-comment"># Become the postgres user</span>
su postgres

<span class="hljs-comment"># Initialise a new empty database</span>
/usr/lib/postgresql/15/bin/initdb --username usr

<span class="hljs-comment"># Run the actual upgrade - you may need to tweak the user</span>
/usr/lib/postgresql/15/bin/pg_upgrade \
    -b /usr/lib/postgresql/13/bin/ -d /mnt/olddata/data \
    -D <span class="hljs-variable">${PGDATA}</span> -U usr
</code></pre>
<p>If that worked, you should see something like:</p>
<pre><code class="lang-bash">Your installation contains extensions that should be updated
with the ALTER EXTENSION <span class="hljs-built_in">command</span>.  The file
    update_extensions.sql
when executed by psql by the database superuser will update
these extensions.

Upgrade Complete
----------------
Optimizer statistics are not transferred by pg_upgrade.
Once you start the new server, consider running:
    /usr/lib/postgresql/16/bin/vacuumdb -U usr --all --analyze-in-stages
</code></pre>
<p>If it told you to run an extension upgrade, run it as suggested (to do that you'll need to start the database as well). If it doesn't tell you, you can just exit the container.</p>
<p>Inside the container, run something like this (note the trailing <code>&amp;</code> on the first command to make it run in the background):</p>
<pre><code class="lang-bash">/usr/lib/postgresql/15/bin/pg_ctl -D /home/postgres/pgdata/data -l logfile start &amp;
psql -U usr -d postgres -f update_extensions.sql
psql -U usr -d postgres -c <span class="hljs-string">'\dx'</span>
</code></pre>
<p>That last command will list the version of the extensions installed: make a note of what it is.</p>
<p>Finally you can type <code>exit</code> twice to get out of the container</p>
<h3 id="heading-step-4-repeat-as-needed">Step 4: Repeat as needed</h3>
<p>If you couldn't upgrade to your desired version in a single step, you'll now need to repeat steps 2 and 3 until you do.</p>
<h3 id="heading-step-5-tidy-up">Step 5: Tidy up</h3>
<p>Finally, undo the changes you made to the Compose file, and delete the old database. You can bring the rest of your services up too, but add the <code>--remove-orphans</code> flag the first time to tidy away the containers used for the upgrade.</p>
<pre><code class="lang-bash">docker compose up --remove-orphans
</code></pre>
]]></content:encoded></item><item><title><![CDATA[ROM upgrade on Asus Nexus 7 2013 (flo)]]></title><description><![CDATA[I have an old Asus Nexus 7 2013 tablet (codename flo) which is still serving me well ten years on, but occasionally I decide I want to put a newer LineageOS ROM on it, or generally reflash it. Unfortunately, I do this sufficiently infrequently that I...]]></description><link>https://blog.samn.co.uk/rom-upgrade-on-asus-nexus-7-2013-flo</link><guid isPermaLink="true">https://blog.samn.co.uk/rom-upgrade-on-asus-nexus-7-2013-flo</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Sun, 22 Jan 2023 13:30:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673916702796/f6115f0c-b1b6-43ed-a592-d39ca3cc1de0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have an old Asus Nexus 7 2013 tablet (codename <code>flo</code>) which is still serving me well ten years on, but occasionally I decide I want to put a newer LineageOS ROM on it, or generally reflash it. Unfortunately, I do this sufficiently infrequently that I can't remember how to, so I wrote myself this guide.</p>
<p>My device has previously had an additional Vendor partition created (so technically it's a <code>flox</code> device rather than <code>flo</code>). That means some more space has been made for some things, but it also means tools like <a target="_blank" href="https://forum.xda-developers.com/t/tool-unofficial-port-repit-for-nexus-7-2013-deb-flo.4237183/">repit</a> won't work for adjusting the size of partitions (see this <a target="_blank" href="https://forum.xda-developers.com/t/rom-flo-deb-unofficial-lineageos-19-1-2022-10-12.3569067/post-86620845">XDA Developers</a> post for more on that). YMMV - that's mainly a note to me!</p>
<p>See here for download links for the ROM I used (should be a zip file): <a target="_blank" href="https://forum.xda-developers.com/t/rom-flo-deb-unofficial-lineageos-19-1-2022-10-12.3569067/">https://forum.xda-developers.com/t/rom-flo-deb-unofficial-lineageos-19-1-2022-10-12.3569067/</a></p>
<p>You'll also want to download OpenGApps (the Google App suite) from that post. You want the <code>pico</code> variant (nothing else will fit) and <code>arm</code> not <code>arm64</code>, for the corresponding Android version to the copy of Lineage you installed (so <code>aos12l</code> at the time of writing).</p>
<p>If you want root access, you'll need a Magisk APK. The forum post where LineageOS came from should list suitable Magisk versions, but you'll need to download it from <a target="_blank" href="https://github.com/topjohnwu/Magisk/releases">https://github.com/topjohnwu/Magisk/releases</a>.</p>
<p>Finally, assuming you've already installed TWRP recovery, you might as well go ahead and update that while you're here. Head to <a target="_blank" href="https://twrp.me/asus/asusnexus72013wifi.html">https://twrp.me/asus/asusnexus72013wifi.html</a>, scroll down to Download Links and choose a suitable mirror, then download the latest <code>.img</code> file.</p>
<h2 id="heading-copying-the-files">Copying the Files</h2>
<p>Once you've downloaded everything, go ahead and reboot the tablet into recovery by turning it off, then holding down the Power and Volume Down keys. Once it starts up, use the volume control to choose Recovery as an option, then push Power to boot.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672522853708/9ef7e985-ee66-4c83-8e69-9c36d9b1a1ee.jpeg" alt class="image--center mx-auto" /></p>
<p>Connect the tablet to your computer: it should mount automatically in the usual way. Then go ahead and copy the files to the root of the tablet.</p>
<h2 id="heading-upgrading-twrp">Upgrading TWRP</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672522060014/77783d63-2b2c-4a6d-b37b-54f63cfbe372.jpeg?height=450" alt class="image--center mx-auto" /></p>
<ol>
<li><p>Select the Install button</p>
</li>
<li><p>Go to Install Image bottom-right</p>
</li>
<li><p>Select the <code>.img</code> file for TWRP</p>
</li>
<li><p>Choose Recovery as the partition to flash</p>
</li>
<li><p>Swipe to confirm flash</p>
</li>
<li><p>Go back to the main screen, select Reboot and then Recovery</p>
</li>
</ol>
<h2 id="heading-installing-lineageos-and-opengapps">Installing LineageOS and OpenGApps</h2>
<ol>
<li><p>Select the Wipe button on the TWRP home screen</p>
</li>
<li><p>Swipe to Factory Reset</p>
</li>
<li><p>Go back to the Wipe screen, select Advanced Wipe</p>
</li>
<li><p>Select System, then swipe to wipe</p>
</li>
<li><p>Go back to the TWRP home screen</p>
</li>
<li><p>Select the Install button</p>
</li>
<li><p>Find the LineageOS zip file, select it</p>
</li>
<li><p>Swipe to install</p>
</li>
<li><p>Find the OpenGApps zip file, select it</p>
</li>
<li><p>Swipe to install</p>
</li>
<li><p>Reboot into the new system</p>
</li>
<li><p>When you're asked if you want to update Recovery with Lineage, don't tick the box: otherwise, it'll just overwrite TWRP for you, which is probably not what you want!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672522256966/b1547761-8248-44f5-91cb-92d5a49e79d0.jpeg?height=450" alt /></p>
</li>
</ol>
<h2 id="heading-installing-magisk">Installing Magisk</h2>
<p>Once booted and you've done the initial setup, find the Magisk APK you downloaded earlier using the Files app, and install it.</p>
<p>Then you'll need to install Magisk itself, by following the instructions at https://topjohnwu.github.io/Magisk/install.html. Note that this device does <em>not</em> have a <code>vbmeta</code> partition (it'll save you a step).</p>
<ol>
<li><p>Unzip the LineageOS zip file from earlier, to extract the <code>boot.img</code> file</p>
</li>
<li><p>Copy this to the tablet</p>
</li>
<li><p>Open the Magisk app, push the Install button</p>
</li>
<li><p>Leave the vbmeta option unchecked and push next</p>
</li>
<li><p>Choose Select and patch file, and find the <code>boot.img</code> file you copied in step 2</p>
</li>
<li><p>Push Let's Go</p>
</li>
<li><p>Shut down the tablet and reboot into recovery (power on with Power and Volume Down again)</p>
</li>
<li><p>Select Install, then Install Image</p>
</li>
<li><p>Find the <code>.img</code> file Magisk created in the Download folder and select it</p>
</li>
<li><p>Select Boot and swipe to confirm</p>
</li>
<li><p>Reboot system</p>
</li>
</ol>
<h2 id="heading-re-flashing-twrp">(Re) Flashing TWRP</h2>
<p>If you need to flash TWRP to start with or like me, you made a mistake and let LineageOS overwrite the recovery the first time, you need to run <code>fastboot flash recovery twrp.img</code> and then <code>fastboot reboot</code> (and then hold Power and Volume Down to get back to the bootloader and TWRP). If you have Docker and are on a Linux system, try these with the container at <a target="_blank" href="https://github.com/mparker17/docker-fastboot">https://github.com/mparker17/docker-fastboot</a> to save mucking about installing the ADB tools.</p>
]]></content:encoded></item><item><title><![CDATA[Run your dual-booted Ubuntu install under Windows with VMWare]]></title><description><![CDATA[First off, this isn't what you might call a "supported" configuration so as ever YMMV and don't blame me if it goes horrifically wrong and you end up with two/three systems that won't boot (your VM, your native install and optionally, the VM host :P)...]]></description><link>https://blog.samn.co.uk/run-your-dual-booted-ubuntu-install-under-windows-with-vmware</link><guid isPermaLink="true">https://blog.samn.co.uk/run-your-dual-booted-ubuntu-install-under-windows-with-vmware</guid><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Wed, 26 Aug 2015 23:00:00 GMT</pubDate><content:encoded><![CDATA[<p>First off, this isn't what you might call a "supported" configuration so as ever YMMV and don't blame me if it goes horrifically wrong and you end up with two/three systems that won't boot (your VM, your native install and optionally, the VM host :P). Having said that it worked fine for me, but it might be wise to make a disk image somewhere else and have a livecd ready if it goes wrong and you need to do recovery. Also you can possibly do this with VirtualBox or VMWare Player, but I haven't tried it. I started with Ubuntu 15.04 installed on my second hard disk in an extended partition along with an NTFS data partition, and Windows 10 on the other disk which is the primary (but is an SSD, so it's small) along with VMWare Workstation 12.</p>
<p>In short we're going to create the VM, then add an extra boot disk and install GRUB into this from the Ubuntu DVD.</p>
<p>First off, create a new VM, choose a Custom machine and go through all the usual steps of names and locations and cores and RAM. When you get to "Select a Disk Type", choose SATA (if you choose SCSI it will whinge on first boot that performance will be poor). Then select "Use a physical disk", set Device to the correct disk (PhysicalDrive1 in my case) and choose Use individual partitions, then tick the relevant partitions for your Linux system (ie the root filesystem and the swap partition).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670273469069/RbzxQHhUo.png" alt="finished_vm_settings.png" /></p>
<p>Click Next a few more times and then Finish (don't hit Customize Hardware, or it doesn't bother adding the disk). Then edit the machine to add a hard disk. This one can be SCSI since it'll be an actual virtual disk, you want to create a new one and set the size for something like 200MB (it's going to have the boot partition, so not much space needed!). Finally mount the Ubuntu 15.04 live disk in the CD drive, as you'll need to boot this first.</p>
<p>Now boot the machine. At this point you might be told "Insufficient permission to access file"; when I had this working in Workstation 9 it would just throw up a UAC prompt but apparently 12 doesn't, so restart VMWare Workstation as administrator to get that low-level disk access we need. Once you've got to the live desktop, open a terminal (Ctrl-Alt-T in Ubuntu). We're going to format our new virtual disk, then chroot to it and install GRUB. To partition and format the new disk, run the following, but first heed this warning: for me the virtual disk was <code>/dev/sda</code>, but I suggest checking with <code>ls /dev/sd*</code> to make sure you don't overwrite your dual-boot disk, as that would be a mess!</p>
<pre><code class="lang-plaintext">parted /dev/sda mklabel msdos
parted /dev/sda mkpart primary ext2 0% 100%
mkfs.ext2 /dev/sda1
</code></pre>
<p>It goes without saying these have to be run as root. Next up comes courtesy of AskUbuntu user Nathan Kidd, on <a target="_blank" href="http://askubuntu.com/a/531051/444031">this question</a> which explains how to chroot to an empty disk; it looks like this:</p>
<pre><code class="lang-plaintext">mkdir /mnt/chrootdir
mount /dev/sda1 /mnt/chrootdir
for dir in proc dev sys etc bin sbin var usr lib lib64 tmp; do
    mkdir /mnt/chrootdir/$dir &amp;&amp; mount --bind /$dir /mnt/chrootdir/$dir
done
chroot /mnt/chrootdir
</code></pre>
<p>Finally we need to install GRUB and then run an update to generate the menus, and we're done!</p>
<pre><code class="lang-plaintext">grub-install
update-grub
</code></pre>
<p>That's it! Exit the chroot, shutdown the VM (it hangs, I had to hard-reset) and remove the ISO, and the next time it gets booted it'll be running the real Ubuntu install on your hard disk. Remember to install the open-vm-tools and open-vm-tools-desktop packages or VMWare Tools to make auto-resize and stuff work. Also I wouldn't suspend the VM and then boot the real copy...that pretty effectively kills both the VM and the underlying install in my experience.</p>
]]></content:encoded></item><item><title><![CDATA[Installing Ubuntu wireless drivers on fresh install]]></title><description><![CDATA[So here's the problem I found myself faced with today; when I ran Ubuntu 15.04 live on my laptop I could pop open the Additional Drivers tool, enable the driver for my wireless card and get on the Internet, lovely. Then I made the (I think very sensi...]]></description><link>https://blog.samn.co.uk/installing-ubuntu-wireless-drivers-on-fresh-install</link><guid isPermaLink="true">https://blog.samn.co.uk/installing-ubuntu-wireless-drivers-on-fresh-install</guid><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Mon, 24 Aug 2015 23:00:00 GMT</pubDate><content:encoded><![CDATA[<p>So here's the problem I found myself faced with today; when I ran Ubuntu 15.04 live on my laptop I could pop open the Additional Drivers tool, enable the driver for my wireless card and get on the Internet, lovely. Then I made the (I think very sensible) assumption that the same would work after I'd installed Ubuntu, until to my surprise, it just sits there thinking about it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670274772523/PikxCaywl.png" alt /></p>
<p>It turns out once you've installed Ubuntu expects an Internet connection and doesn't use the CD (or USB stick in my case), not ideal when it's the network driver and you don't have wired in your house. In principle you should be able to go to Ubuntu Software under Software &amp; Updates and just select the CDROM option, then tell it to install the driver.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670274810609/YXGR76d7n.png" alt /></p>
<p>No dice, it still just sits there. However <a target="_blank" href="http://askubuntu.com/a/9465">this AskUbuntu question</a> (found using my phone!) points to the commands to do it from a terminal, which usually gives a bit more helpful info. First run <code>ubuntu-drivers devices</code> which gives the name of the package to install, then attempt to install the package by the usual means.</p>
<pre><code class="lang-plaintext">root@buccaneer-linux:/home/sam# ubuntu-drivers devices == /sys/devices/pci0000:00/0000:00:1c.1/0000:03:00.0 == modalias : pci:v000014E4d00004358sv0000105Bsd0000E040bc02sc80i00 model : BCM43227 802.11b/g/n vendor : Broadcom Corporation driver : bcmwl-kernel-source - distro non-free

\== cpu-microcode.py == driver : intel-microcode - distro non-free

root@buccaneer-linux:/home/sam# apt-get install bcmwl-kernel-source Reading package lists... Done Building dependency tree  
Reading state information... Done The following extra packages will be installed: dkms The following NEW packages will be installed bcmwl-kernel-source dkms 0 to upgrade, 2 to newly install, 0 to remove and 217 not to upgrade. Need to get 0 B/1,574 kB of archives. After this operation, 8,390 kB of additional disk space will be used. Do you want to continue? \[Y/n\] y Media Change: Please insert the disc labelled 'Ubuntu 15.04 \_Vivid Vervet\_ - Release amd64 (20150422)' in the drive ‘/media/cdrom/’ and press enter
</code></pre>
<p>At this point I put my USB stick back in and symlinked it (<code>ln -s /media/sam/UUI /media/cdrom</code> if you don't know) and pressed Enter, which presented me with exactly the same message again. Apparently doing that just makes Ubuntu helpfully unmount the USB stick!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670274935052/sPA9ppR7T.png" alt /></p>
<p>The solution is to add the USB stick as a separate software source, although identifying the correct syntax took some trial and error. Under the Other Software tab, hit Add, then enter a line something like below, hit Add Source, do <code>apt-get update</code> (there will be a lot of "failed to fetch" errors!) and then try the install.</p>
<pre><code class="lang-plaintext">deb file:///media/sam/UUI vivid main restricted
</code></pre>
<p>However (and this is where I had trouble and had to run the update quite a few times), the three words after the path depend on the distribution. So, on your USB stick in the folder you specify (<code>UUI</code> in my case) should be a <code>dists</code> folder and in that folder should be another folder for the distribution - <code>vivid</code> in my case. The final two refer to the components available and should match the subfolders of <code>vivid</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670274953679/Kdvb1cOkI.png" alt /></p>
<p>After you've run the update the install should finally succeed with <code>apt-get install bcmwl-kernel-source</code> in my case, and then all that's left is to remove your new software source.</p>
<h1 id="heading-tldr">tl;dr</h1>
<p>Add a new software source like <code>deb file:///media/usbstickmountpoint vivid main restricted</code>, run <code>ubuntu-drivers devices</code> then <code>apt-get install</code> the relevant package under "driver" Hope that helps someone!</p>
]]></content:encoded></item><item><title><![CDATA[How to auto-reload binaries in GNU ARM Eclipse]]></title><description><![CDATA[I've been working on my final year project using the GNU ARM Eclipse plugin and the STM32F4 Discovery board to write some simple signal generation software. Eclipse has a button to reset the target and restart debugging on the toolbar, and I figured ...]]></description><link>https://blog.samn.co.uk/how-to-auto-reload-binaries-in-gnu-arm-eclipse</link><guid isPermaLink="true">https://blog.samn.co.uk/how-to-auto-reload-binaries-in-gnu-arm-eclipse</guid><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Tue, 13 Jan 2015 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I've been working on my final year project using the <a target="_blank" href="http://gnuarmeclipse.livius.net/">GNU ARM Eclipse plugin</a> and the STM32F4 Discovery board to write some simple signal generation software. Eclipse has a button to reset the target and restart debugging on the toolbar, and I figured it would also download a new binary if there was one. Unfortunately it doesn't, and I couldn't find any explanation how to start debugging, find a problem, make a change, recompile and reload the software without stopping and restarting the debugger, which isn't all that quick.</p>
<p>Turns out its really easy, at the bottom of the Startup tab in the Eclipse Debug Configurations box is a space for commands to run after a reset, and it looks like that's run when you hit the reset and restart button too. The commands I used were:</p>
<pre><code class="lang-plaintext">symbol-file C:\\\\Users\\\\Sam\\\\...someotherstuff...\\\\Debug\\\\projectname.elf
load C:\\\\Users\\\\Sam\\\\...someotherstuff...\\\\Debug\\\\projectname.elf
</code></pre>
<p>It looks like the double backslash is important to escape the path properly, and obviously I've changed the path and remove the project name to protect the guilty!</p>
<p>Hope that helps someone, or possibly me if I forget how to do this!</p>
]]></content:encoded></item><item><title><![CDATA[Inexplicably slow hard disks? Try disabling PIO mode]]></title><description><![CDATA[A baffling problem I came across recently was a server where copying between internal disks ran painfully slowly, getting around 3.3MB/s between internal SATA drives. Of the machine's three disks, two were failing with huge numbers of ATA errors and ...]]></description><link>https://blog.samn.co.uk/inexplicably-slow-hard-disks-try-disabling-pio-mode</link><guid isPermaLink="true">https://blog.samn.co.uk/inexplicably-slow-hard-disks-try-disabling-pio-mode</guid><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Sat, 26 Apr 2014 23:00:00 GMT</pubDate><content:encoded><![CDATA[<p>A baffling problem I came across recently was a server where copying between internal disks ran painfully slowly, getting around 3.3MB/s between internal SATA drives. Of the machine's three disks, two were failing with huge numbers of ATA errors and bad sectors, so I pulled those out an ran test copies to and from the remaining system disk, with no improvement. Incidentally, to find out that the drives were failing I used a brilliant little tool called <a target="_blank" href="https://code.google.com/p/hddguardian/">HDD Guardian</a> which reads <a target="_blank" href="http://en.wikipedia.org/wiki/S.M.A.R.T.">SMART</a> monitoring values the same way smartctl does, just with a nice graphical interface.</p>
<p>It turns out the server had defaulted to IDE mode on the disk controller as Server 2003 doesn't really contain any SATA AHCI drivers and you'd need a floppy disk to install them. In of itself this wouldn't cause an issue, but when Windows detects multiple CRC errors on a disk transfer, it will reduce the transfer rate. Eventually the disk will become stuck in Programmed Input/Output or PIO Mode, which essentially means all data transfers must pass through the narrow CPU IO bus rather than going via system memory which chews up a lot of CPU time. There's a lot more info on this topic at <a target="_blank" href="http://wiki.osdev.org/ATA_PIO_Mode">http://wiki.osdev.org/ATA_PIO_Mode</a>.</p>
<p>To fix this, there are some instructions in <a target="_blank" href="https://support.microsoft.com/kb/817472">KB817472</a> in the Microsoft Knowledgebase and some other instructions in <a target="_blank" href="http://techlogon.com/2011/03/28/how-to-fix-hard-drive-stuck-in-pio-mode/">this TechLogon article</a>, neither of which fixed the problem. Since the server in question is a VM host and all the VMs were copied onto other systems anyway when the drives failed, I just reinstalled it with Server 2008 and the disks set in AHCI mode, which is cheating, but it made the problem go away. Now my transfer speeds are back to normal, so I just have to replace the two 500GB drives with 4000 reallocated sectors. Drive magnets anyone?</p>
]]></content:encoded></item><item><title><![CDATA[My flight simulator experience]]></title><description><![CDATA[Today I got the opportunity to fly a Boeing 747-400! Cue cheesey grin in shoddy phone photograph:

OK, so it wasn't a real one. British Airways ran a giveaway over Christmas and I was lucky enough to win one of three simulator sessions at the Craneba...]]></description><link>https://blog.samn.co.uk/my-flight-simulator-experience</link><guid isPermaLink="true">https://blog.samn.co.uk/my-flight-simulator-experience</guid><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Thu, 17 Apr 2014 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670276518024/QPqyq3mji.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today I got the opportunity to fly a Boeing 747-400! Cue cheesey grin in shoddy phone photograph:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670275427451/DHgsUAbOS.jpg" alt /></p>
<p>OK, so it wasn't a real one. British Airways ran a giveaway over Christmas and I was lucky enough to win one of three simulator sessions at the Cranebank training centre where BA train and certify their pilots. After a couple of emails and fighting my way through the rush hour traffic heading into London I dutifully showed up at a painfully early time this morning (8:15 is a dirty word among students!).</p>
<p>I was introduced to Andy who would be my pilot and instructor for the session, and ushered through a maze of corridors to the simulator itself. The simulators at Cranebank are full-motion, so they are basically a cockpit wrapped in screens, all supported on big hydraulic jacks that make it feel like real flight. The inevitable safety briefing followed and like everything in aviation redundancy was everywhere; in an emergency the simulator would 'land' and the access bridge would lower, and if that failed there was a ladder, and if the simulator angle stopped the ladder coming down there was a scramble net! Andy pointed out they'd never even needed the ladder before as the bridge had always worked</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670275514148/0U2KslRNU.png" alt /></p>
<p>Some years ago I learned to fly a Grob Vigilant motor glider and even made a solo circuit, so we went through the controls and what the instruments did fairly quickly, paying particular attention to some of the automatic systems that make these aircraft (relatively) easy to fly. One such system is the Flight Director (pictured above, courtesy Wikipedia), which means altitudes, headings and climb rates can be programmed into the autopilot panel and pink lines on the <a target="_blank" href="http://en.wikipedia.org/wiki/Primary_flight_display">PFD</a> tell the pilot where to point the plane.</p>
<p>We set up for a takeoff on runway 27L at Heathrow, and I immediately got confused as the conditions were set for early evening but I'd arrived in brilliant sunshine at 8AM, which just goes to show how realistic the simulator is. You could even see headlights from the cars driving along the A30 and M25 near the runway! I made a rather wobbly takeoff that just about stayed on the runway, then flew a couple of turns, climbs and descents to try and get used to the aircraft, it feels a lot more sluggish than the little gliders did!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670275574400/ju70Ecq1l.jpg" alt /></p>
<p>Up next was the big one, landing again. Andy talked me through it and handled all the aircraft setup like flaps and landing gear, and after nearly missing the runway and going quite a long way down it before touching the ground, it just about worked out. Oddly I landed quite a long way down the runway on my solo glider flight too after completely stuffing the approach angle and speed. Still, what is it they say, a good landing is any one you can walk away from and even better if you can reuse the plane!</p>
<p>We reset the simulator to 12 miles out to try again, which is where things went a bit wrong. The autopilot was engaged and setup to capture the <a target="_blank" href="http://en.wikipedia.org/wiki/Instrument_landing_system">ILS</a> (autoland...ish) at Heathrow and the flight freeze was released. The throttles immediately advanced to full power and the whole cockpit shuddered a lot while Andy said "I'm not really sure I like that vibration" as he hastily went for the freeze button again. We reset the simulator, reset some of the controls and changed some configuration, shut off the autopilot and autothrottle this time, and tried again with much the same result. I noticed that the engine power gauges are still sat at idle despite the throttles being in the middle, and it looked to all appearances like we were dropping straight down towards Clapham when Andy hit the freeze button again.</p>
<p>It turned out that above the power gauges the "REV" indicator was still on, showing the thrust reversers were still extended after the earlier landing. This shouldn't be possible in flight and the lever won't even move unless the plane is on the ground - for good reason too as it makes the plane fall out of the sky! To get them to close again we called another Andy who had set up our simulator session and he pulled all the flight management computer circuit breakers, positioned us back on the ground and let the reversers close, then reset everything again to clear the problem.</p>
<p>As a minor technical note, particularly topical at the time I'm writing this; there is a huge panel of circuit breakers above the pilot's head in the 747, including two clearly labeled ACARS. For all the news media reporting on the tragic disappearance of MH370 and how detailed technical knowledge is needed to turn ACARS off in the 777, it's certainly easy to turn off in the 747!</p>
<p>After a slightly more successful landing (well I landed at the right end of the runway) Andy showed me what a category 3 autoland looks like in thick fog. Imagine staring at a blank white sheet, until at 50 feet above the ground a runway materialises out of nowhere and a second later you hit it, hoping that the autopilot has set everything up properly!</p>
<p>Finally we went for a change of scenery and took off out of Geneva in Switzerland, climbing out towards the Alps before turning back for my neatest landing yet, in that I stopped in the middle of the runway even if I touched down quite a long way to the left!</p>
<p>We'd run out of time by then, but the experience was absolutely fantastic, and I'd like to say a big thank you to British Airways for setting up the competition, and to Andy C and Andy S for a great morning out.</p>
]]></content:encoded></item><item><title><![CDATA[The astonishing connected world]]></title><description><![CDATA[Ever looked at a vision of the future from, say, 30 years ago? According to those 1980s authors, our future contains such excitements as flying cars, living in space and colonies on the moon. Unfortunately, none of these things have really materialis...]]></description><link>https://blog.samn.co.uk/the-astonishing-connected-world</link><guid isPermaLink="true">https://blog.samn.co.uk/the-astonishing-connected-world</guid><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Sat, 02 Feb 2013 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670276568528/PbgpXu0QQ.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ever looked at a vision of the future from, say, 30 years ago? According to those 1980s authors, our future contains such excitements as flying cars, living in space and colonies on the moon. Unfortunately, none of these things have really materialised; although we've made progress in quite a few of them. However, one thing almost never predicted is every individual wandering around, always connected, always linked to the sum of human knowledge, able to share thoughts and experiences with the entire world as a matter of course.</p>
<p>Naturally I'm talking about the Internet, and the evolution of the smartphone and the widening spread of mobile data, all of which add up to rich, connected applications pervading our daily lives. For many of us, we can't imagine life without always-on connectivity, and are completely stuck when our phones/tablets/whatever break or run out of power. Going beyond the mundane desire to tweet pictures of what I had for dinner, connectivity has even played a part in overthrowing dictatorships and corrupt regimes. If I want to know the situation in Mali, learn how to fix my car, or just watch a cat falling down the stairs repeatedly, its available near-instantly, wherever and whenever I want it.</p>
<p>Today I've worked with people I'll never meet, on a product they'll never see, I've pair-programmed with a friend 80 miles away as if he were sat next to me, and I've watched a music video performed by 40 people, from radically different backgrounds, scattered all over the world. The likes of YouTube have made unlikely celebrities (Numa Numa guy anyone?) and launched countless careers and forced the music and broadcasting industries to turn established ideas on their head to stay relevant in today's connected world.</p>
<p>Looking to the future, I'm hesitant to speculate what will come given the start to this post; and there are a great many terrifyingly plausible visions of our future, however I think its safe to say that it'll be interesting.</p>
]]></content:encoded></item><item><title><![CDATA[PCB Etching at home]]></title><description><![CDATA[Whilst working on a new [Tally](http://en.wikipedia.org/wiki/Tally_light "Tally") system for YSTV, I decided making a PCB for the Arduino control board was a better option than the three separate veroboards I was trying to use, which would probably o...]]></description><link>https://blog.samn.co.uk/pcb-etching-at-home</link><guid isPermaLink="true">https://blog.samn.co.uk/pcb-etching-at-home</guid><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Sat, 05 Jan 2013 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670274336219/rLEvA8Ocs.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Whilst working on a new [Tally](http://en.wikipedia.org/wiki/Tally_light "Tally") system for YSTV, I decided making a PCB for the Arduino control board was a better option than the three separate veroboards I was trying to use, which would probably only work once.</p>
<p>PCB etching works by applying an etch resist to a sheet of copper-clad laminate, then using an etching chemical to dissolve the copper not covered by resist, leaving the circuit behind. If tight manufacture tolerances aren't required this can be a DIY job with two main approaches. The first is to buy special [photo-etch boards](http://www.rapidonline.com/tools-equipment/photoetch-pcb-29445/ "...which can be bought from Rapid and similar"), print the circuit layout onto transparency, and use a lightbox to expose the board, with etch-resist remaining in the unexposed portions. This is potentially more accurate, but also more expensive for lightboxes and boards. The other method, which I used, is to transfer laser toner (from a laser printer) onto the copper-clad board, and the plastic-based toner will resist the etchant.</p>
<p>Naturally the first step was to design and layout a circuit. For this I used Eagle, with a set of design rules (setting trace and pad sizes and clearances) from <a target="_blank" href="http://www.ianstedman.co.uk/downloads/homepcb.dru">http://www.ianstedman.co.uk/downloads/homepcb.dru</a>. After bad experiences with autorouters before I routed mine by hand, taking care to minimise vias to the top layer. As etching a dual-layer PCB accurately using this method is almost impossible, the handful of traces that ran on the other layer would be point-to-point wire links (in blue on the layout). </p>
<p>The resulting PCB layout and schematic:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670273877910/9TV0Crd_W.png" alt="TallyLayout.png" />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670273900718/Ew3mv4ult.png" alt="TallySchematic.png" class="image--right mx-auto mr-0" /></p>
<p>For toner-transfer to work it has to be heated to the point it melts and sticks to the board. With conventional laser paper this leaves bits of paper stuck to the circuit board, which will make the board look naff and soldering tricky. Glossy paper (such as magazine or photo paper) will break apart and come off in water, making it an ideal medium to use for the transfer. You can also buy special transfer paper, which is essentially the same thing. I used an old magazine I happened to have, but be careful not to choose anything too thin or it will jam in a printer. </p>
<p>By far the easiest way to transfer toner to the copper board is to iron it on, but be careful where you put the iron (as a nasty hole in my carpet will testify). Iron for quite a long time with plenty of heat (but no steam!), as if the paper is peeled off too early the etch won't work, and once its off it won't line back up. After peeling the paper off, run the board under warm water for a while and rub it gently (your fingers will do) to take of the rest of the glossy paper. </p>
<p>I attempted to clean up some of the traces which didn't transfer correctly with a fine-point Sharpie, but it didn't work out particularly well as the ink came off the board when I etched it.</p>
<p>Removing the remaining bits of paper after the transfer, and the layout transferred ready for ething
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670273960150/qwQim9J7g.jpg" alt="RemovingPaper.jpg" />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670274100422/6SALTahtk.jpg" alt="TransferedBoard.jpg" /></p>
<p>The next step is etching, but if you haven't already, trim the board to size. Fibreglass (the laminate holding the copper) is a tough substance to be prepared to blunt a conventional hacksaw, buy an expensive one, or just score it with a sharp knife and snap it (my approach). </p>
<p>There are a couple of etchant solutions available, but the most common is Ferric Chloride (FeCl), which can be bought in pellet form from most electronics suppliers (I got mine from Maplin).</p>
<p>Boring Health and Safety note: Ferric Chloride is extremely corrosive, and will attack any kind of metal, and more to the point, you. Wear eye protection and gloves, don't lick it, drink it, use it to wash yourself, feed it to your dog, you know the drill. Also, it stains clothes, so wear something you don't like. These were my preparations:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670274153908/L67TkMa2M.jpg" alt="DSC00961.jpg" /></p>
<p>I made a hanger out of insulated wire to suspend my board in the solution (the insulation protects the metal wire) as in the photo below, and sacrificed a sponge as well to speed up the process. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670274191519/_RvLLwrmp.jpg" alt="hanger.jpg" /></p>
<p>The tricky bit with etching is to take the board out once all the copper is gone that you didn't want, but before the etchant has eaten away under the resist and destroyed the circuit you wanted. This bit didn't go so well for me, I think I left it in a little too long after I gave up wiping it with the sponge. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670274238441/C0RmEVi0G.jpg" alt="FinishedBoard.jpg" /></p>
<p> A drop of acetone (nail varnish remover) will remove the etch resist leaving a nice shiny PCB to begin soldering. In my case some repairs were needed as some of the traces weren't joined up anymore. Trying to repair with solder bridges is almost impossible, so I recommend sacrificing some resistors and laying segments of their legs across the gaps. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670274263385/CiJjs2_dt.jpg" alt="Finished.jpg" /></p>
<p>Above is the final result, with repairs done and most of the components populated. The board works, but it certainly isn't ideal given how long the repairs to fix broken traces took. From this experience, I would suggest if there is enough time (~1.5 months), take a look at [http://www.seeedstudio.com/depot/fusion-pcb-service-p-835.html Fusion PCB] who will produce 10 2-layer 5cmx5cm boards with change from £15. <a target="_blank" href="http://samn.co.uk/2013/01/pcb-etching-at-home/tallycontrol/">Eagle Files</a></p>
]]></content:encoded></item><item><title><![CDATA[Hello!]]></title><description><![CDATA[I've finally set up a blog, something I've been meaning to do for about 4 months! Over the next few weeks I'll start writing all the posts I've had ideas for over the last few weeks and months and actually put them up here, so watch this space!
In th...]]></description><link>https://blog.samn.co.uk/hello</link><guid isPermaLink="true">https://blog.samn.co.uk/hello</guid><dc:creator><![CDATA[Sam Mesterton-Gibbons]]></dc:creator><pubDate>Sun, 09 Sep 2012 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I've finally set up a blog, something I've been meaning to do for about 4 months! Over the next few weeks I'll start writing all the posts I've had ideas for over the last few weeks and months and actually put them up here, so watch this space!</p>
<p>In the meantime, a little about me: I'm currently studying for a degree in Electronic Engineering at the University of York, going in to what should be my third, and penultimate year, however I'm taking a year out at the moment to do an Industrial Placement with Renesas Electronics Europe through the UK Electronic Skills Foundation <a target="_blank" href="http://www.ukesf.org/scholarship-scheme">scholarship scheme</a>. In my free time I'm also a techie at <a target="_blank" href="http://ystv.co.uk">York Student Television</a> where I do hardware/software development and maintenance on various bits of broadcast equipment, although more in a supporting role this year since I'm "in exile" as it were.</p>
<p>It goes without saying that everything written on here represents my own opinions, not those of my employer, York Student Telelvision nor anyone else I am related to. Find me on LinkedIn as <a target="_blank" href="http://www.linkedin.com/pub/sam-nicholson/43/816/4ab">Sam Nicholson</a> and on twitter as <a target="_blank" href="http://twitter.com/samdnic">@samdnic</a></p>
]]></content:encoded></item></channel></rss>