<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>dev.endevour</title><link>https://devendevour.iankulin.com/</link><description>Recent content on dev.endevour</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Sat, 10 Jan 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://devendevour.iankulin.com/index.xml" rel="self" type="application/rss+xml"/><item><title>VS Code Dev Containers</title><link>https://devendevour.iankulin.com/vs-code-dev-containers/</link><pubDate>Sat, 10 Jan 2026 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/vs-code-dev-containers/</guid><description>&lt;h3 id="remote-ssh"&gt;Remote-SSH&lt;/h3&gt; &lt;p&gt;One of the things I&amp;rsquo;ve done a bit in Visual Studio Code is using it&amp;rsquo;s ability to work on a different machine over SSH. I have a couple of LXCs on a server set up for different languages - one for C++ and another for Rust. They are things I don&amp;rsquo;t work in often, and I didn&amp;rsquo;t want to set them up on my laptop, but thought I might want them again sometime in the future.&lt;/p&gt;</description></item><item><title>Getting Ghostty to Work on Synology</title><link>https://devendevour.iankulin.com/getting-ghostty-to-work-on-synology/</link><pubDate>Mon, 28 Jul 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/getting-ghostty-to-work-on-synology/</guid><description>&lt;p&gt;Ghostty is a terminal application that I don&amp;rsquo;t really &lt;em&gt;need&lt;/em&gt; (it&amp;rsquo;s &lt;a href="https://ghostty.org/docs/about" target="_blank" rel="noopener"&gt;listed features&lt;/a&gt; either already exist in the MacOS terminal, or seem so esoteric or marginal that I can&amp;rsquo;t imagine any real benefit from them in my normal use), but I &lt;em&gt;wanted&lt;/em&gt; to be one of the cool kids, so I thought I&amp;rsquo;d give it a try.&lt;/p&gt;
&lt;p&gt;After fiddling around with the themes for a bit I renamed it to &amp;rsquo;term-ghosty.app&amp;rsquo; so I&amp;rsquo;d remember to use it (ie when I pop up spotlight and type &amp;rsquo;term&amp;rsquo; it will come up) and got on with my day. Ten minutes later I&amp;rsquo;d run into a problem.&lt;/p&gt;</description></item><item><title>State of AI tooling (for me)</title><link>https://devendevour.iankulin.com/state-of-ai-tooling-for-me/</link><pubDate>Mon, 07 Jul 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/state-of-ai-tooling-for-me/</guid><description>&lt;p&gt;I&amp;rsquo;ve been meaning to write this for a couple of weeks, so let&amp;rsquo;s get to it - things are moving to fast to reflect too long; which is it&amp;rsquo;s own risk.&lt;/p&gt;
&lt;p&gt;In March, I wrote about &lt;a href="https://devendevour.iankulin.com/where-im-up-to-with-ai-for-coding/"&gt;how I was using AI in coding&lt;/a&gt; , which was Codeium (now Windsurf) in VS Code for completions, and ChatGPT and Claude online for architecture questions and code gen that was more than half a function.&lt;/p&gt;</description></item><item><title>Writing a Browser Extension</title><link>https://devendevour.iankulin.com/writing-a-browser-extension/</link><pubDate>Sun, 22 Jun 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/writing-a-browser-extension/</guid><description>&lt;p&gt;Web pages are mostly just a collection of HTML, CSS, and JavaScript, so if we had some way of adding some of these into a web page, perhaps from our browser we could add new behaviour to a web page, right?&lt;/p&gt;
&lt;p&gt;Yes; users have long used tools like Greasemonkey (or similar userscript managers) to inject scripts into pages. Better still, modern browsers expose JavaScript APIs that let us interact directly with the browser itself. Enter: browser extensions.&lt;/p&gt;</description></item><item><title>End to end testing - Cypress basics</title><link>https://devendevour.iankulin.com/end-to-end-testing-cypress-basics/</link><pubDate>Mon, 12 May 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/end-to-end-testing-cypress-basics/</guid><description>&lt;p&gt;When you&amp;rsquo;ve made a change to your web-app, do you run it then click around the new bits to check it works? Good start, but instead of doing that yourself, do it in a faster, more comprehensive and automated way with an end-to-end (E2E) testing setup using &lt;a href="https://www.cypress.io/" target="_blank" rel="noopener"&gt;Cypress&lt;/a&gt; . Here&amp;rsquo;s how.&lt;/p&gt;
&lt;h3 id="e2e"&gt;E2E&lt;/h3&gt; &lt;p&gt;End to End testing is testing your app as a user might - by clicking links, entering data, looking at the screen and checking everything is okay, but it&amp;rsquo;s scripted like a unit test and the results are checked with assertions. Like unit testing this allows you to build up a collection of comprehensive tests that easily detect for unexpected behaviours - not just in the results of functions in your app, but in the user experience of the app.&lt;/p&gt;</description></item><item><title>Express router for better code organisation</title><link>https://devendevour.iankulin.com/express-router-for-better-code-organisation/</link><pubDate>Mon, 28 Apr 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/express-router-for-better-code-organisation/</guid><description>&lt;p&gt;A Node/Express app I&amp;rsquo;m working on has been sprouting routes so much that the &lt;code&gt;server.js&lt;/code&gt; file has swollen to 800 lines - way past my 200-250 comfort zone, so it&amp;rsquo;s time to organise the routes into their own files. That seems like a good topic for a beginner blog post, so let&amp;rsquo;s dive in.&lt;/p&gt;
&lt;p&gt;Imagine we&amp;rsquo;ve written this little Node/Express app.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import express from &amp;#34;express&amp;#34;;
import {
 dbCustomersGet,
 dbCustomersGetById,
 dbCustomersDelete,
 dbOrdersGet,
 dbOrdersGetById,
 dbOrdersGetByCustomerId,
 dbOrdersDelete,
} from &amp;#34;./db.js&amp;#34;;

const app = express();
app.set(&amp;#34;view engine&amp;#34;, &amp;#34;ejs&amp;#34;);
const port = 3002;

app.use(express.urlencoded({ extended: true }));

app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 res.redirect(&amp;#34;/customers&amp;#34;);
});

app.get(&amp;#34;/customers&amp;#34;, (req, res) =&amp;gt; {
 const customers = dbCustomersGet();
 res.render(&amp;#34;customers&amp;#34;, { customers });
});

app.get(&amp;#34;/customers/:id&amp;#34;, (req, res) =&amp;gt; {
 const customer = dbCustomersGetById(req.params.id);
 const orders = dbOrdersGetByCustomerId(req.params.id);
 res.render(&amp;#34;customer&amp;#34;, { customer, orders });
});

app.get(&amp;#34;/customers/:id/delete&amp;#34;, (req, res) =&amp;gt; {
 dbCustomersDelete(req.params.id);
 res.redirect(&amp;#34;/customers&amp;#34;);
});

app.get(&amp;#34;/orders&amp;#34;, (req, res) =&amp;gt; {
 const orders = dbOrdersGet();
 res.render(&amp;#34;orders&amp;#34;, { orders });
});

app.get(&amp;#34;/orders/:id&amp;#34;, (req, res) =&amp;gt; {
 const order = dbOrdersGetById(req.params.id);
 const customer = dbCustomersGetById(order.customerId);
 res.render(&amp;#34;order&amp;#34;, { order, customer });
});

app.get(&amp;#34;/orders/:id/delete&amp;#34;, (req, res) =&amp;gt; {
 dbOrdersDelete(req.params.id);
 res.redirect(&amp;#34;/orders&amp;#34;);
});

app.listen(port, () =&amp;gt; {
 console.log(`Listening on http://127.0.0.1:${port}`);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Although concocted, this would seem familiar to anyone who&amp;rsquo;s built a CRUD business app.&lt;/p&gt;</description></item><item><title>Functional Javascript array methods</title><link>https://devendevour.iankulin.com/functional-javascript-array-methods/</link><pubDate>Mon, 14 Apr 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/functional-javascript-array-methods/</guid><description>&lt;p&gt;I&amp;rsquo;ve been whipping up a little mock-database unit that has a few access functions but actually stores the data as arrays for a demo project for a post I&amp;rsquo;m writing. In the process I wrote this gem:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;export function dbOrdersAdd(order) {
 const orderCopy = { ...order };
 // since id is a stringified number, finding the max is a bit of a mess
 const maxId = orders.reduce((max, o) =&amp;gt; Math.max(max, parseInt(o.id)), 0);
 orderCopy.id = String(maxId + 1);
 orders.push(orderCopy);
 return { ...orderCopy };
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the comment I&amp;rsquo;m claiming the code is a bit of a mess (and from a readability point that&amp;rsquo;s true) but actually I love the elegance of using the &lt;code&gt;reduce()&lt;/code&gt; method here.&lt;/p&gt;</description></item><item><title>Manually adding SSL certs in Nginx Proxy Manager</title><link>https://devendevour.iankulin.com/manually-adding-ssl-certs-in-nginx-proxy-manager/</link><pubDate>Mon, 31 Mar 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/manually-adding-ssl-certs-in-nginx-proxy-manager/</guid><description>&lt;p&gt;A large part of the reason for my use of Nginx Proxy manager over vanilla NGINX, is that it has built-in Let&amp;rsquo;s Encrypt certificate requesting and renewing. This works perfectly for all my public facing services, and until recently, my homelab services. Before I dive into how I&amp;rsquo;ve fixed the problem I ran into, I better explain how my homelab domain is set up, and before I do that, an over-simplified description of how the SSL system works is required&lt;/p&gt;</description></item><item><title>Node.js built in test runner</title><link>https://devendevour.iankulin.com/node-js-built-in-test-runner/</link><pubDate>Mon, 17 Mar 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/node-js-built-in-test-runner/</guid><description>&lt;p&gt;For the longest time, I&amp;rsquo;ve been using &lt;a href="https://mochajs.org/" target="_blank" rel="noopener"&gt;Mocha&lt;/a&gt; (test runner) and &lt;a href="https://www.chaijs.com/" target="_blank" rel="noopener"&gt;Chai&lt;/a&gt; (assertion library) for my JS testing. They are reliable old friends.&lt;/p&gt;
&lt;p&gt;One of the effects of the existence of &lt;a href="https://bun.sh/" target="_blank" rel="noopener"&gt;Bun&lt;/a&gt; and &lt;a href="https://deno.com/" target="_blank" rel="noopener"&gt;Deno&lt;/a&gt; has been to spur Node onto adding some new features, so after appearing as an experimental feature in 18, the Node test runner dropped in Node 20.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure if the familiar unit test layout of Mocha and Node is inherited from Jest, or comes from older testing frameworks of which JUnit and NUnit were the first ones I&amp;rsquo;d ever used. Before that I just used to write tests as lumps of assertions in regular code - which worked but wasn&amp;rsquo;t as pleasant to use as a proper unit test setup. Regardless, the system of bundling a few tests together and having them all run and spit out green ticks is not a new one.&lt;/p&gt;</description></item><item><title>Where I'm up to with AI for coding</title><link>https://devendevour.iankulin.com/where-im-up-to-with-ai-for-coding/</link><pubDate>Mon, 03 Mar 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/where-im-up-to-with-ai-for-coding/</guid><description>&lt;p&gt;There&amp;rsquo;s still plenty of controversy about LLMs for coding, and not without reason. But I thought I&amp;rsquo;d run through what I&amp;rsquo;ve tried, and where I&amp;rsquo;ve landed for using AI. Also what the pitfalls are, where it&amp;rsquo;s useful and how it&amp;rsquo;s changed my practice.&lt;/p&gt;
&lt;h3 id="issues"&gt;Issues&lt;/h3&gt; &lt;h5 id="training-data"&gt;Training data&lt;/h5&gt; &lt;p&gt;The training data for large language models generally is problematic. There&amp;rsquo;s no doubt that they have been trained on copyright material. With code it&amp;rsquo;s slightly less murky since there is a high availability of good quality open source data with attached licenses to train models on. No doubt this include code written by people who don&amp;rsquo;t approve of it being used by AI, but I think the popular reading of most open source licenses is that using it for training is fine.&lt;/p&gt;</description></item><item><title>A bit of web-scraping with Cheerio</title><link>https://devendevour.iankulin.com/a-bit-of-web-scraping-with-cheerio/</link><pubDate>Mon, 17 Feb 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/a-bit-of-web-scraping-with-cheerio/</guid><description>&lt;p&gt;I had an idea for a little holiday project that required a list of episodes from &lt;a href="https://therestishistory.supportingcast.fm/" target="_blank" rel="noopener"&gt;The Rest Is History&lt;/a&gt; podcast. On their &amp;lsquo;Episodes&amp;rsquo; page, they have a player, and a list of post entries for the most recent eighteen podcasts. There is a &amp;lsquo;show all&amp;rsquo; button, but it doesn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2025-01-05-at-8.47.03-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The player does contain the full list of episodes (about 600) including a number of duplicates, so I expected if I inspected the network calls that I&amp;rsquo;d see a JSON package arriving with what I wanted. This is what I almost always find these days so I&amp;rsquo;ve had very little call to do any real web scraping - it&amp;rsquo;s normally just a matter of locating the endpoint and perhaps extracting an API key from a header.&lt;/p&gt;</description></item><item><title>Command chaining with NTFY for long running commands</title><link>https://devendevour.iankulin.com/command-chaining-with-ntfy-for-long-running-commands/</link><pubDate>Mon, 03 Feb 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/command-chaining-with-ntfy-for-long-running-commands/</guid><description>&lt;p&gt;&lt;a href="https://ntfy.sh/" target="_blank" rel="noopener"&gt;NTFY&lt;/a&gt; is a great open-source push notification service that&amp;rsquo;s self-hostable or free to use (although I suggest you &lt;a href="https://liberapay.com/ntfy" target="_blank" rel="noopener"&gt;pay for it&lt;/a&gt; as I do). I&amp;rsquo;ve written before how I use it with &lt;a href="https://devendevour.iankulin.com/uptime-kuma-nfty/"&gt;UptimeKuma&lt;/a&gt; for my uptime monitoring, but another common use is just when I&amp;rsquo;m initiating long-running commands and backgrounding them.&lt;/p&gt;
&lt;p&gt;This magic is possible since we can just &lt;code&gt;curl&lt;/code&gt; to send a NTFY notification. For example:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl -d &amp;#34;😀 demo push message via NTFY&amp;#34; ntfy.sh/blog_demo
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since I&amp;rsquo;m subscribed to the &amp;ldquo;blog_demo&amp;rdquo; topic in NTFY, this message will be pushed to my phone and watch:&lt;/p&gt;</description></item><item><title>Share files securely with Enclosed</title><link>https://devendevour.iankulin.com/share-files-securely-with-enclosed/</link><pubDate>Mon, 27 Jan 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/share-files-securely-with-enclosed/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-12-05-at-7.53.56-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;My accountant works for one of those giant firms, and it bugs me that I&amp;rsquo;m emailing him password protected zip files of my accounts rather than to a secure upload facility at his firm. I can fix this with the power of self hosting, by running my own secure file dropping app on a VPS.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a number of applications that &lt;a href="https://github.com/awesome-selfhosted/awesome-selfhosted?tab=readme-ov-file#file-transfer---single-click--drag-n-drop-upload" target="_blank" rel="noopener"&gt;do this sort of thing&lt;/a&gt; - allow you to upload a file, get a link in return which you can then share to people to download the file. For this to be more secure than emailing, the file needs to be encrypted on the server, and we want to be able to set a password, impose limits on downloads, and limit how long the link lives for. I&amp;rsquo;ve previously looked at &lt;a href="https://github.com/eikek/sharry" target="_blank" rel="noopener"&gt;Sharry&lt;/a&gt; which adds the ability for unauthenticated users to &lt;em&gt;upload&lt;/em&gt; files to you securely, but for this slightly simpler job, I chose &lt;a href="https://github.com/CorentinTh/enclosed" target="_blank" rel="noopener"&gt;Enclosed&lt;/a&gt; by &lt;a href="https://corentin.tech/" target="_blank" rel="noopener"&gt;Corentin Thomasset&lt;/a&gt; .&lt;/p&gt;</description></item><item><title>Moving a Docker image as a file</title><link>https://devendevour.iankulin.com/moving-a-docker-image-as-a-file/</link><pubDate>Mon, 20 Jan 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/moving-a-docker-image-as-a-file/</guid><description>&lt;p&gt;I&amp;rsquo;m having a super annoying problem at the moment, I can&amp;rsquo;t pull down containers from DockerHub. If I hotspot my laptop off my phone it works fine, so it&amp;rsquo;s some drama with the home internet connection that rebooting the router does not fix.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve had a couple of different errors including &lt;code&gt;Error response from daemon: Get &amp;quot;https://registry-1.docker.io/v2/&amp;quot;: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)&lt;/code&gt; and &lt;code&gt;Error response from daemon: Get &amp;quot;https://registry-1.docker.io/v2/&amp;quot;: dial tcp: lookup registry-1.docker.io&lt;/code&gt;. I can&amp;rsquo;t actually ping &lt;code&gt;registry-1.docker.io&lt;/code&gt; or &lt;code&gt;hub.docker.com&lt;/code&gt;, although I can open hub.docker.com in a browser, so it works for ports 80 and 443, but not some other udp ports.&lt;/p&gt;</description></item><item><title>Perils of Benchmarking</title><link>https://devendevour.iankulin.com/perils-of-benchmarking/</link><pubDate>Mon, 06 Jan 2025 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/perils-of-benchmarking/</guid><description>&lt;p&gt;I&amp;rsquo;ve been containerising my websites, with their servers to make deployment simple and robust, and to move to a CI/CD workflow. Since an install of a production web server is large, I would be running about ten of these containers, and there&amp;rsquo;s already a good server facing the net and doing the reverse-proxying (NGINX Proxy Manager), I chose to bundle the Busy-Box httpd server with my sites inside the Docker containers.&lt;/p&gt;</description></item><item><title>Moving a domain from Wordpress</title><link>https://devendevour.iankulin.com/moving-a-domain-from-wordpress/</link><pubDate>Mon, 30 Dec 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/moving-a-domain-from-wordpress/</guid><description>&lt;p&gt;I love the convenience of a hosted blog on wordpress.com, but one of the justifications for my &amp;lsquo;investment&amp;rsquo; in homelab hardware and learning time was that I&amp;rsquo;d reduce my spend on hosted platforms by self-hosting them. I&amp;rsquo;ve already quit Evernote and dropped back to the free plan on Dropbox by building systems to replace them for less money and more data sovereignty. And now, the recent &lt;a href="https://techcrunch.com/2024/09/25/wordpress-org-bans-wp-engine-blocks-it-from-accessing-its-resources/" target="_blank" rel="noopener"&gt;Wordpress drama&lt;/a&gt; has made me uneasy about Matt having control of domains I&amp;rsquo;ve got registered with wordpress.&lt;/p&gt;</description></item><item><title>Updating a deployment on fly.io</title><link>https://devendevour.iankulin.com/updating-a-deployment-on-fly-io/</link><pubDate>Mon, 16 Dec 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/updating-a-deployment-on-fly-io/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/flyio_picture.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve had my external UptimeKuma chugging away on &lt;a href="https://fly.io/" target="_blank" rel="noopener"&gt;fly.io&lt;/a&gt; , for free, for months now, and the container image it was based on was a bit out of date, so I wanted to update it. I hadn&amp;rsquo;t looked at fly.io for months, and couldn&amp;rsquo;t really recall what I&amp;rsquo;d done to create it.&lt;/p&gt;
&lt;p&gt;The way this works is that that you create a fly.toml file that sets out the details of your app. From memory I think I used the one from the docs and gave it a unique name, the name of the Docker image, the port, the datacentre location, and the directory for the persisted data. The you run &lt;code&gt;fly deploy&lt;/code&gt; from the directory with the toml file (having already installed the CLI tool and logged in) and you&amp;rsquo;re in business.&lt;/p&gt;</description></item><item><title>NGINX proxy manager - setting headers to use basic auth in your apps</title><link>https://devendevour.iankulin.com/nginx-proxy-manager-setting-headers-to-use-basic-auth-in-your-apps/</link><pubDate>Mon, 09 Dec 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/nginx-proxy-manager-setting-headers-to-use-basic-auth-in-your-apps/</guid><description>&lt;p&gt;When I&amp;rsquo;m spinning up side projects, I frequently ignore auth, and just rely on NGINX basic auth - one of the side benefits of reverse-proxying everything.&lt;/p&gt;
&lt;h3 id="regular-nginx"&gt;Regular NGINX&lt;/h3&gt; &lt;p&gt;This &lt;a href="https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/" target="_blank" rel="noopener"&gt;article in the docs&lt;/a&gt; explains how to set up basic auth to protect different paths. To make it work in my node apps, I need the successful user name passed in so I check it against the user table to work out access rights etc.&lt;/p&gt;</description></item><item><title>Controlling Docker container startup order</title><link>https://devendevour.iankulin.com/controlling-docker-container-startup-order/</link><pubDate>Mon, 02 Dec 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/controlling-docker-container-startup-order/</guid><description>&lt;p&gt;A very common scenario when running services in Docker containers is that one service is going to depend on another. The most common example is going to be if you have a service that needs a database - you&amp;rsquo;re going to want the container running the database to be ready for business before the service that needs it starts. And conversely, when you shut things down, you want to stop the service before you kill the database or you may lose some data.&lt;/p&gt;</description></item><item><title>Fixing TLS for wget in BusyBox</title><link>https://devendevour.iankulin.com/fixing-tls-for-wget-in-busybox/</link><pubDate>Mon, 25 Nov 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/fixing-tls-for-wget-in-busybox/</guid><description>&lt;p&gt;I&amp;rsquo;ve been containerising my static websites with BusyBox (because it&amp;rsquo;s small), and in &lt;a href="https://devendevour.iankulin.com/fancier-website-in-a-docker-container/"&gt;an earlier post&lt;/a&gt; showed how to even get the container to update parts of the site by reaching out with &lt;code&gt;wget&lt;/code&gt; to download resources from elsewhere and saving them inside the container where we are serving the &amp;lsquo;static&amp;rsquo; site from. I&amp;rsquo;d done this by including a bash script in the container with the &lt;code&gt;wget&lt;/code&gt; in a loop with a &lt;code&gt;sleep&lt;/code&gt;. Then started the script and the httpd server in the CMD line of the &lt;code&gt;dockerfile&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Fancier Website in a Docker Container</title><link>https://devendevour.iankulin.com/fancier-website-in-a-docker-container/</link><pubDate>Mon, 18 Nov 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/fancier-website-in-a-docker-container/</guid><description>&lt;p&gt;The previous post went over how to bundle a static website into a Docker container. That&amp;rsquo;s a neat little trick - keeping the entire website and making it trivial to install on a VPS behind Nginx Proxy Manager. It worked great for most of my little websites.&lt;/p&gt;
&lt;h3 id="but"&gt;But&amp;hellip;&lt;/h3&gt; &lt;p&gt;A couple of my websites had very minor &amp;lsquo;dynamic&amp;rsquo; content. One was pulling down the local temperature from OpenWeather, then exposing a cut-down version of that as a REST endpoint so all my servers could grab it without me being rate-limited by OpenWeather for abusing my free API key. Another one re-hosted an image that changes a couple of times a day from an unreliable service.&lt;/p&gt;</description></item><item><title>Website in a Docker Container</title><link>https://devendevour.iankulin.com/website-in-a-docker-container/</link><pubDate>Mon, 11 Nov 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/website-in-a-docker-container/</guid><description>&lt;p&gt;Having figured out how to use the GitHub package registry, I was a bit inspired by &lt;a href="https://lipanski.com/posts/smallest-docker-image-static-website" target="_blank" rel="noopener"&gt;this blog post&lt;/a&gt; from Florin Lipan to deliver all my little static websites as Docker containers. I&amp;rsquo;m not as focused as he is about making them tiny, but I did steal the idea of using &lt;a href="https://busybox.net/about.html" target="_blank" rel="noopener"&gt;BusyBox&lt;/a&gt; httpd for serving them, resulting in about 4MB containers. That&amp;rsquo;s small enough for me, and since they are all very similar, there&amp;rsquo;s a fair bit of layer reuse going on.&lt;/p&gt;</description></item><item><title>Using the GitHub Container Registry</title><link>https://devendevour.iankulin.com/using-the-github-container-registry/</link><pubDate>Mon, 04 Nov 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/using-the-github-container-registry/</guid><description>&lt;p&gt;As the number of little projects I&amp;rsquo;m running on VPSs grows, I need to have a regimented system for managing all that. I could be using something like &lt;a href="https://coolify.io/" target="_blank" rel="noopener"&gt;Coolify&lt;/a&gt; , but, at least for the moment, I&amp;rsquo;d rather build my own system.&lt;/p&gt;
&lt;p&gt;Currently my system is Nginx Proxy Manager (dockerised) in front of each app. If it&amp;rsquo;s a static website, that&amp;rsquo;s another dockerised Nginx, started with a compose file and with &lt;code&gt;www&lt;/code&gt; and &lt;code&gt;conf&lt;/code&gt; sub-directories that I&amp;rsquo;ve &lt;code&gt;git pull&lt;/code&gt;ed from the project. It&amp;rsquo;s not pretty.&lt;/p&gt;</description></item><item><title>Clear explanation of how transformer AI works</title><link>https://devendevour.iankulin.com/clear-explanation-of-how-transformer-ai-works/</link><pubDate>Mon, 28 Oct 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/clear-explanation-of-how-transformer-ai-works/</guid><description>&lt;p&gt;If you&amp;rsquo;re interested in how generative AI works, check out &lt;a href="https://ishananand.com/" target="_blank" rel="noopener"&gt;Ishan Anand&lt;/a&gt; &amp;rsquo;s Youtube series &amp;ldquo;&lt;a href="https://www.youtube.com/@Spreadsheetsareallyouneed" target="_blank" rel="noopener"&gt;Spreadsheets are all you need&lt;/a&gt; &amp;rdquo;. He steps through the basics using an Excel spreadsheet that encompasses most of GPT-2. Just doing that is an impressive (and hilarious) feat, but he also has a knack for teaching, so you&amp;rsquo;ll come away with a good understanding of AI and how some of it&amp;rsquo;s limitations manifest.&lt;/p&gt;
&lt;p&gt;Ishan is selling a course, which I guess these are the first three lessons of, and I got a lot out of them. It&amp;rsquo;s also possible to &lt;a href="https://github.com/ianand/spreadsheets-are-all-you-need/releases/tag/v0.6.1" target="_blank" rel="noopener"&gt;download the spreadsheet&lt;/a&gt; he uses in the course to play with.&lt;/p&gt;</description></item><item><title>npm ERR! Exit handler never called!</title><link>https://devendevour.iankulin.com/npm-err-exit-handler-never-called/</link><pubDate>Mon, 21 Oct 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/npm-err-exit-handler-never-called/</guid><description>&lt;p&gt;I quite like GitHub scanning all my code and sending me security advisories. Here&amp;rsquo;s today&amp;rsquo;s:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-09-27-at-11.31.03-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;With these, and my &lt;a href="https://github.com/dependabot" target="_blank" rel="noopener"&gt;dependabot&lt;/a&gt; alerts, fixing them is usually just a matter of pulling down the project, running an &lt;code&gt;npm update&lt;/code&gt;, building any artifacts, then pushing it back up. But today, not so:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-09-27-at-11.36.57-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;h3 id="package-lockjson"&gt;package-lock.json&lt;/h3&gt; &lt;p&gt;It&amp;rsquo;s probably worth revisiting what the &lt;code&gt;package-lock.json&lt;/code&gt; does. It contains all the versions of any packages you&amp;rsquo;ve imported, and their dependencies. The idea is that this will make the build reproducible. We don&amp;rsquo;t commit the node_modules folder (that actually contains all that package code), but npm can reproduce it exactly by using the version information in the package-lock.json file. Here&amp;rsquo;s a snippet where you can see all those versions:&lt;/p&gt;</description></item><item><title>Code reuse by publishing to NPM</title><link>https://devendevour.iankulin.com/code-reuse-by-publishing-to-npm/</link><pubDate>Mon, 14 Oct 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/code-reuse-by-publishing-to-npm/</guid><description>&lt;p&gt;If you find yourself copying over a source file from one Node project to another because it&amp;rsquo;s a handy utility you wrote and are used to using, you&amp;rsquo;re only doing it half right. A better way to do this is to publish your utility to the &lt;a href="https://www.npmjs.com" target="_blank" rel="noopener"&gt;Node Package Manager&lt;/a&gt; (NPM). That way you can just import your utility where ever you need it, it will live in the &lt;code&gt;node_modules&lt;/code&gt; of any project that uses it, and most importantly, updates are sorted out automatically - because that&amp;rsquo;s what package managers are good at.&lt;/p&gt;</description></item><item><title>rsync between Synology NAS</title><link>https://devendevour.iankulin.com/rsync-between-synology-nas/</link><pubDate>Mon, 30 Sep 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/rsync-between-synology-nas/</guid><description>&lt;p&gt;A while ago, I devised a complicated system where I could drop files in a web interface running on an LXD container and the files would then magically appear in a directory on a remote NAS in the morning. It turned out to not be very robust, and I gave up on it after a while.&lt;/p&gt;
&lt;p&gt;Also, really there should be no need for it - underneath, it was just using &lt;code&gt;rsync&lt;/code&gt; to move the files, so why not just do that direct from one NAS to another? Well, mainly because my NASs are all Synology - which I love, and they&amp;rsquo;ve been great, but in an effort to make them usable by muggles, Synology tend to somewhat complicate things for Linux command line wizards.&lt;/p&gt;</description></item><item><title>Containerised NGINX Proxy Manager &amp;amp; the 502 error</title><link>https://devendevour.iankulin.com/containerised-nginx-proxy-manager-the-502-error/</link><pubDate>Mon, 16 Sep 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/containerised-nginx-proxy-manager-the-502-error/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-08-24-at-6.46.49-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re used to running NGINX Proxy Manager in front of your web apps, and switch to running it in a container, you&amp;rsquo;re going to need to learn a little about Docker networks to get everything connected. If you just do your regular setup, and direct the proxy for an address to &lt;code&gt;127.0.0.1:&amp;lt;some port&amp;gt;&lt;/code&gt;, it won&amp;rsquo;t exist, and you&amp;rsquo;ll visit your page to find the &amp;ldquo;502 Bad Gateway openresty&amp;rdquo; message.&lt;/p&gt;</description></item><item><title>Uploading files to a web app with Node</title><link>https://devendevour.iankulin.com/uploading-files-to-a-web-app-with-node/</link><pubDate>Mon, 02 Sep 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/uploading-files-to-a-web-app-with-node/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-08-18-at-3.09.38-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;My default approach to web apps at the moment is Node/Express SSR. I needed to have users be able to upload files this week, and as usual there&amp;rsquo;s an express middleware that makes it trivial. This post just steps through using &lt;a href="https://github.com/expressjs/multer" target="_blank" rel="noopener"&gt;multer&lt;/a&gt; to make it simple to enable file uploads on your website.&lt;/p&gt;
&lt;h3 id="express--middleware"&gt;Express &amp;amp; middleware&lt;/h3&gt; &lt;p&gt;Before we look at file uploading, it&amp;rsquo;s worth just explaining how it fits with the other tools we&amp;rsquo;re using:&lt;/p&gt;</description></item><item><title>Authentication basics for Node apps</title><link>https://devendevour.iankulin.com/authentication-basics-for-node-apps/</link><pubDate>Mon, 19 Aug 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/authentication-basics-for-node-apps/</guid><description>&lt;p&gt;&lt;a href="https://unsplash.com/photos/calahorra-tower-torre-de-la-calahorra-in-cordoba-spain-a-fortified-gate-built-during-the-late-12th-century-by-the-almohads-to-protect-the-nearby-roman-bridge-in-the-historic-center-of-cordoba-andalusia-spain-ECsukeqrDoo" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-08-10-at-8.59.01-pm.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Pretty much every serious web app needs to include a way for users to log in securely and to be served their content. Since there&amp;rsquo;s a lot of complexity in this, it&amp;rsquo;s highly advisable to use good libraries to support this. In a future post we&amp;rsquo;re going to use those libraries, but first I want to explain what&amp;rsquo;s happening at the lower level and tease out some of the concepts as we build a secure system from the ground up.&lt;/p&gt;</description></item><item><title>Moving from Docker volumes to bind mounts</title><link>https://devendevour.iankulin.com/moving-from-docker-volumes-to-bind-mounts/</link><pubDate>Mon, 05 Aug 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/moving-from-docker-volumes-to-bind-mounts/</guid><description>&lt;p&gt;&lt;a href="https://placesjournal.org/article/all-is-lost-notes-on-broken-world-design/" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/friedman-moe-lost-6.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;When I started with Docker, the docs seemed to suggest that using Docker volumes was a good thing. With a Docker volume, you just create the volume and Docker manages the rest. You don&amp;rsquo;t have to worry about where it is, or really ever think about it.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a docker-compose for &lt;a href="https://github.com/louislam/uptime-kuma/wiki" target="_blank" rel="noopener"&gt;Uptime Kuma&lt;/a&gt; using a volume.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;services:
 uptime-kuma:
 image: louislam/uptime-kuma:1
 container_name: uptime-kuma
 volumes:
 - kuma_data:/app/data
 ports:
 - 80:3001
 restart: unless-stopped

volumes:
 kuma_data:
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is telling Docker we want to create a volume called &amp;ldquo;kuma_data&amp;rdquo; and then map it into the container file system at &lt;code&gt;/app/data&lt;/code&gt;&lt;/p&gt;</description></item><item><title>LLM coding question comparison using Ollama</title><link>https://devendevour.iankulin.com/llm-coding-question-comparison-using-ollama/</link><pubDate>Mon, 29 Jul 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/llm-coding-question-comparison-using-ollama/</guid><description>&lt;p&gt;Now Ollama has made it simple enough for anyone who can use a terminal to run large language models locally, naturally I&amp;rsquo;ve gone overboard downloading too many to play with. I&amp;rsquo;m increasingly feeling they definitely have a place in the devops/coding arsenal of tools, but which model is best?&lt;/p&gt;
&lt;p&gt;If you go on HuggingFace to look at a new model you&amp;rsquo;re interested, they often have great comparisons like this.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://huggingface.co/deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/performance.png" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;</description></item><item><title>dockerfile - CMD vs ENTRYPOINT</title><link>https://devendevour.iankulin.com/dockerfile-cmd-vs-entrypoint/</link><pubDate>Mon, 22 Jul 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/dockerfile-cmd-vs-entrypoint/</guid><description>&lt;p&gt;There are two entries we often have at the end of a &lt;code&gt;dockerfile&lt;/code&gt; (which is the file that tells Docker how an image is to be built).&lt;/p&gt;
&lt;p&gt;They are similar in that when the container is launched from an image, these commands will be executed. For example, both of the dockerfiles below will print &amp;ldquo;Hello World&amp;rdquo; when run.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;doc-&lt;/code&gt;entry:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;FROM debian:stable-slim
ENTRYPOINT [&amp;#34;echo&amp;#34;, &amp;#34;Hello World from ENTRYPOINT&amp;#34;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;doc-cmd&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;FROM debian:stable-slim
CMD [&amp;#34;echo&amp;#34;, &amp;#34;Hello World&amp;#34;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-07-03-at-1.45.26-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;</description></item><item><title>User environment variables are not available in cron</title><link>https://devendevour.iankulin.com/user-environment-variables-are-not-available-in-cron/</link><pubDate>Mon, 15 Jul 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/user-environment-variables-are-not-available-in-cron/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-07-02-at-4.13.13-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m used to using the &lt;code&gt;docker-compose.yaml&lt;/code&gt; or &lt;code&gt;dockerfile&lt;/code&gt; to set environment variables for containers running my apps, but ran into an issue recently where the variable seemed to be set some of the time, but at others it didn&amp;rsquo;t appear to exist.&lt;/p&gt;
&lt;p&gt;I had a script set to run by &lt;code&gt;cron&lt;/code&gt; inside the container, and it turns out that the environment variables set for the container are available in the user space, but not in &lt;code&gt;cron&lt;/code&gt;, even if running with that user&amp;rsquo;s permissions. This is probably old news to established Linux users but it threw me for a while. I&amp;rsquo;d &lt;code&gt;exec&lt;/code&gt; into the container and the script would work perfectly, then wait another minute for &lt;code&gt;cron&lt;/code&gt; to run it and it would fail 🤦‍♀️ It was exasperated by my discovery that I didn&amp;rsquo;t know how to console.log debug from inside a container cron job as well - the subject of an earlier post.&lt;/p&gt;</description></item><item><title>Outputting to the console, in Docker, from a cron job</title><link>https://devendevour.iankulin.com/outputting-to-the-console-in-docker-from-a-cron-job/</link><pubDate>Mon, 08 Jul 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/outputting-to-the-console-in-docker-from-a-cron-job/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-07-02-at-3.48.02-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re googling this exact title, you&amp;rsquo;re probably bumping your head against the same things I was today. I was debugging a completely different project, and needed to print to the console, from a &lt;code&gt;cron&lt;/code&gt; job, in a Docker container. Turns out this isn&amp;rsquo;t as straightforward as I thought.&lt;/p&gt;
&lt;h3 id="foreground-cron"&gt;Foreground cron&lt;/h3&gt; &lt;p&gt;Before you even get to the problem space, here&amp;rsquo;s a tip. If you want to have a cron job running in a container, start &lt;code&gt;cron&lt;/code&gt; in the foreground. If you do not, Docker realises nothing is going on, and exits. If you want to keep the container active so your &lt;code&gt;cron&lt;/code&gt; jobs get a chance to execute, then start it in the foreground.&lt;/p&gt;</description></item><item><title>Using LLMs for coding</title><link>https://devendevour.iankulin.com/using-llms-for-coding/</link><pubDate>Mon, 01 Jul 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/using-llms-for-coding/</guid><description>&lt;p&gt;&lt;a href="https://madmuseum.org/events/ghost-shell" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/ghost-in-the-shell_07.jpg" alt="Ghost in the Shell
© Manga Entertainment 1996
" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;This post looks at the context for some of my thinking about AI for supporting software development, and where I&amp;rsquo;ve landed on it for the time being.&lt;/p&gt;
&lt;h3 id="the-landscape"&gt;The landscape&lt;/h3&gt; &lt;p&gt;I &lt;a href="https://devendevour.iankulin.com/chatgpts-code-writing/"&gt;briefly wrote about ChatGPT&amp;rsquo;s&lt;/a&gt; coding ability at the end of 2022. The wide availability of this tool marked the beginning of what I think can fairly be described as a revolution. The controversies that have crystalised since have not dampened my amazement of this step forward in what compute can do, especially around natural language processing.&lt;/p&gt;</description></item><item><title>SSH login notification</title><link>https://devendevour.iankulin.com/ssh-login-notification/</link><pubDate>Mon, 13 May 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ssh-login-notification/</guid><description>&lt;p&gt;&lt;a href="https://unsplash.com/photos/brown-bell-on-white-concrete-wall-4VRzuA4UxSY?utm_content=creditShareLink&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/nick-fewings-4vrzua4uxsy-unsplash.jpg" alt="Photo by Nick Fewings Unsplash
" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;My VPS&amp;rsquo;s are usually locked down so just ports 80 &amp;amp; 443 (for web server) and 22 (for ssh) are open. That&amp;rsquo;s great for reducing the attack surface, but having ssh open is a potentially disastrous vulnerability. For this reason I often close that at the cloud firewall level as well, but it has to be open when I&amp;rsquo;m making changes or running the weekly ansible update/cleanup playbooks.&lt;/p&gt;</description></item><item><title>Upgrading to Forgejo 7.0.1</title><link>https://devendevour.iankulin.com/upgrading-to-forgejo-7-0-1/</link><pubDate>Mon, 06 May 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/upgrading-to-forgejo-7-0-1/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-04-28-at-1.08.21-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not that long ago that &lt;a href="https://devendevour.iankulin.com/my-web-app-update-process/"&gt;I wrote about&lt;/a&gt; doing routine upgrades on containerised web apps using Forgejo as an example as I upgraded Forgejo (my git repository manager) between patch versions of 1.21, then a few days later, they dropped 7.0.0&lt;/p&gt;
&lt;p&gt;&lt;a href="https://forgejo.org/2024-04-release-v7-0/" target="_blank" rel="noopener"&gt;They say&lt;/a&gt; the major version jump is due to it being an LTS (long term support) release, and changing to &lt;a href="https://semver.org/spec/v2.0.0.html" target="_blank" rel="noopener"&gt;semantic versioning 2.0.0&lt;/a&gt; , but that doesn&amp;rsquo;t quite explain it to me, and I assume this is partly signifying the fork&amp;rsquo;s drift away from the gitea codebase. In any case, the upgrade to 7.0.0 it does involve some breaking changes, and signifies to me that a lot has been on, which makes me keen to wait for a patch release (I&amp;rsquo;m always keen for other people to debug these things) which has now landed.&lt;/p&gt;</description></item><item><title>Peek inside a Docker image</title><link>https://devendevour.iankulin.com/peek-inside-a-docker-image/</link><pubDate>Mon, 29 Apr 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/peek-inside-a-docker-image/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-04-25-at-10.20.28-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A &amp;lsquo;dockerfile&amp;rsquo; contains all the instructions to build a Docker image. Here&amp;rsquo;s my first draft for a project I&amp;rsquo;m working on:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;FROM node:20
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD [&amp;#34;node&amp;#34;, &amp;#34;server.js&amp;#34;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;COPY . .&lt;/code&gt; is copying all of the files in my project into the working directory of the image so they can be run. Of course we don&amp;rsquo;t need them all for the app - for example the &lt;code&gt;node_modules&lt;/code&gt; directory will be created when we &lt;code&gt;npm install&lt;/code&gt; so no need to copy that, and I don&amp;rsquo;t need all my dot files in the container.&lt;/p&gt;</description></item><item><title>Virtual Hosts on "Static Web Server"</title><link>https://devendevour.iankulin.com/virtual-hosts-on-static-web-server/</link><pubDate>Mon, 22 Apr 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/virtual-hosts-on-static-web-server/</guid><description>&lt;p&gt;I&amp;rsquo;ve been running &lt;a href="https://devendevour.iankulin.com/nginx-proxy-manager/"&gt;NGINX Proxy Manager&lt;/a&gt; (NPM) in my homelab for a bit, and I&amp;rsquo;ve been meaning to clean up the VPS that runs most of my websites and public facing servers, so I&amp;rsquo;m considering running NGINX Proxy Manager on that VPS. While NGINX Proxy Manager wraps up the configs in a beautiful GUI, in the process you lose some of NGINXs capabilities. In particular there&amp;rsquo;s no GUI way to serve static virtual hosts from NGINX Proxy Manager.&lt;/p&gt;</description></item><item><title>NGINX Proxy Manager</title><link>https://devendevour.iankulin.com/nginx-proxy-manager/</link><pubDate>Mon, 15 Apr 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/nginx-proxy-manager/</guid><description>&lt;p&gt;I&amp;rsquo;ve mentioned using NGINX as an &lt;a href="https://devendevour.iankulin.com/nginx-in-front-of-a-node-js-app/"&gt;interface between the internet and a service&lt;/a&gt; a while ago. This works by all incoming traffic coming to NGINX, and NGINX determining which service that traffic should go (from the NGINX config files) then acting as a middleman. This functionality is generally referred to as a &amp;lsquo;reverse proxy&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/nginx.png" alt="Terrible drawing of NGINX proxying requests off to different services." class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;This is nice for a few reasons:&lt;/p&gt;</description></item><item><title>Due Diligence on a Docker Image</title><link>https://devendevour.iankulin.com/due-diligence-on-a-docker-image/</link><pubDate>Mon, 08 Apr 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/due-diligence-on-a-docker-image/</guid><description>&lt;p&gt;&lt;a href="https://unsplash.com/photos/gray-figure-ELLDKLrXMoA" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/brett-jordan-elldklrxmoa-unsplash.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; 
&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@brett_jordan?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" target="_blank" rel="noopener"&gt;Brett Jordan&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/gray-figure-ELLDKLrXMoA?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" target="_blank" rel="noopener"&gt;Unsplash&lt;/a&gt; &lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I need a survey tool, and a quick search turned up &lt;a href="https://www.limesurvey.org/" target="_blank" rel="noopener"&gt;LimeSurvey&lt;/a&gt; , there&amp;rsquo;s a &amp;lsquo;community edition&amp;rsquo; so naturally I plan to self-host it. I scrolled down to the &amp;lsquo;installation&amp;rsquo; section of the &lt;a href="https://manual.limesurvey.org/Installation_-_LimeSurvey_CE/en" target="_blank" rel="noopener"&gt;manual&lt;/a&gt; which has a big list of PHP dependencies.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-03-29-at-7.20.31-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Ain&amp;rsquo;t nobody got the time for that in 2024, I scroll further looking for the docker-compose but there isn&amp;rsquo;t one. Huh. No official Docker image.&lt;/p&gt;</description></item><item><title>My Web App Update Process</title><link>https://devendevour.iankulin.com/my-web-app-update-process/</link><pubDate>Mon, 01 Apr 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/my-web-app-update-process/</guid><description>&lt;p&gt;I&amp;rsquo;ve settled on a very standard, reproducible setup for services in my homelab. This post looks at that, then runs through the update I did today to Forgejo which only took a few minutes and felt relatively risk free.&lt;/p&gt;
&lt;h3 id="standard-setups"&gt;Standard Setups&lt;/h3&gt; &lt;p&gt;My system is based around Proxmox. I have three physical machines - one for production apps, a production spare, and a development/testbed machine. A Synology NAS serves for backups. Moving a VM or LXC between the machines is trivial; but it&amp;rsquo;s done manually - the machines are not clustered for high availability.&lt;/p&gt;</description></item><item><title>Deploying a Node app in Docker</title><link>https://devendevour.iankulin.com/deploying-a-node-app-in-docker/</link><pubDate>Sun, 31 Mar 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/deploying-a-node-app-in-docker/</guid><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Cargo_ship#/media/File:Cargo_Ship_Puerto_Cortes.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/cargo_ship_puerto_cortes.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;When I wrote the install instructions for mdserver (little Markdown server Node app) on it&amp;rsquo;s &lt;a href="https://github.com/IanKulin/mdserver" target="_blank" rel="noopener"&gt;github page&lt;/a&gt; it was something like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Have node.js installed and working&lt;/li&gt;
&lt;li&gt;Clone the repo&lt;/li&gt;
&lt;li&gt;Start with &lt;code&gt;npm start&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which is great if you know &lt;a href="https://devendevour.iankulin.com/installing-a-node-app-on-a-server/"&gt;how to do those things&lt;/a&gt; (they are bread and butter to a web dev) but not if you&amp;rsquo;re a self-hoster who just wants a web server that converts markdown to HTML on the fly. For any situation where you just want to use the app, what you probably want is a Docker image of the app.&lt;/p&gt;</description></item><item><title>Hosting Your Own Docker Registry</title><link>https://devendevour.iankulin.com/hosting-your-own-docker-registry/</link><pubDate>Mon, 25 Mar 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/hosting-your-own-docker-registry/</guid><description>&lt;p&gt;&lt;a href="https://unsplash.com/photos/architectural-photography-of-cargo-containers-stack-hP4ZiN1_kdk?utm_content=creditShareLink&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/tri-eptaroka-mardiana-hp4zin1_kdk-unsplash.jpg" alt="Photo by Tri Eptaroka Mardianam on Unsplash
" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;The Docker &lt;a href="https://docs.docker.com/subscription/core-subscription/details/" target="_blank" rel="noopener"&gt;Personal (ie free tier) plan&lt;/a&gt; currently allows one private repository, but even if you want to pay for the next level where you can have unlimited repositories, you may still want to host your own private registry - it&amp;rsquo;s going to be quicker inside your network, and you won&amp;rsquo;t run up against Docker&amp;rsquo;s pull/push limits if you are hammering it with your CI/CD system.&lt;/p&gt;</description></item><item><title>Certbot - removing a domain</title><link>https://devendevour.iankulin.com/certbot-removing-a-domain/</link><pubDate>Mon, 18 Mar 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/certbot-removing-a-domain/</guid><description>&lt;p&gt;I had a number of domains all running on one host when I first set them up with certbot. One started to be serious, so I moved it to another host and ran certbot there. That all worked perfectly, but of course, the old domain is still part of the original certificate, so when I went to renew it, it came up with some errors.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a few commands that are going to help navigate this situation if you&amp;rsquo;ve found yourself in the same spot:&lt;/p&gt;</description></item><item><title>Quick &amp;amp; Dirty auth with nginx &amp;amp; Node</title><link>https://devendevour.iankulin.com/quick-dirty-auth-with-nginx-node/</link><pubDate>Fri, 23 Feb 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/quick-dirty-auth-with-nginx-node/</guid><description>&lt;p&gt;One of the basic requirements for any serious web app is a proper users/roles/authentication system - but if you&amp;rsquo;re just throwing up a utility of some kind on a public IP for testing, and you don&amp;rsquo;t want it to be abused, then this could be an option. There&amp;rsquo;s a few components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your app. In this demo it&amp;rsquo;s going to be Node, but it could be Go or whatever your server-side poison is. The app is listening for connections on a non-web port (ie not on 80 or 443), I&amp;rsquo;m going to use the traditional 3000.&lt;/li&gt;
&lt;li&gt;A firewall. That port (in my example 3000) must not be accessible from the internet. It has to be blocked by a firewall.&lt;/li&gt;
&lt;li&gt;A web server (I&amp;rsquo;m using nginx) that enforces basic auth.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I briefly discussed web server basic auth earlier - it&amp;rsquo;s a system built into the web server that requires a log in for a route, and authenticates it against the credentials in a password file (usually named &lt;code&gt;.htpasswrd&lt;/code&gt;) and only serves the content if authenticated.&lt;/p&gt;</description></item><item><title>Beginning Node App Security</title><link>https://devendevour.iankulin.com/beginning-node-app-security/</link><pubDate>Fri, 16 Feb 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/beginning-node-app-security/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/sciacqualani_digital_paint_illustration_of_padlock_in_a_cyber_w_6a902b1c-29a3-4f98-9f6b-411d9594550c.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;m using Tailscale to painlessly manage all my networking on the homeserver here and my remotes, I&amp;rsquo;ve had the luxury of being a bit casual about the security of my internal apps and self hosted dev tools. I&amp;rsquo;m currently iterating on a web app that requires public access, and is therefore up on a VPS and exposed to all the evils of the open internet.&lt;/p&gt;
&lt;p&gt;I am in no way a security expert, but here&amp;rsquo;s a few of the (reasonably simple) steps I&amp;rsquo;ve taken to secure my node app.&lt;/p&gt;</description></item><item><title>User Sessions &amp;amp; Cookies in Node</title><link>https://devendevour.iankulin.com/user-sessions-cookies-in-node/</link><pubDate>Fri, 09 Feb 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/user-sessions-cookies-in-node/</guid><description>&lt;p&gt;When you are learning app development, you can create all sorts of apps that work for you, but for any serious app, it&amp;rsquo;s going to need to authenticate users and persist sessions across visits. So much so, that as a professional developer, you&amp;rsquo;ll probably build that out first - it becomes a sort of boiler plate you always drop in.&lt;/p&gt;
&lt;p&gt;In this post, focusing on the server side, using node, express, and particularly express-session, I&amp;rsquo;ll try and build up from nothing to a reasonable usable user login system explaining the increasing complexity and reasons for it. To follow along you&amp;rsquo;ll need basic familiarity with node and express.&lt;/p&gt;</description></item><item><title>Web Development Overview</title><link>https://devendevour.iankulin.com/web-development-overview/</link><pubDate>Mon, 05 Feb 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/web-development-overview/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2024-01-27-at-1.00.35-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t often just link to someone else&amp;rsquo;s content, but I was really impressed with &lt;a href="https://www.traversymedia.com/" target="_blank" rel="noopener"&gt;Brad Traversy&lt;/a&gt; &amp;rsquo;s &amp;ldquo;Web Development In 2024 - A Practical Guide&amp;rdquo; video. Apparently he does these every year - it&amp;rsquo;s just a really comprehensive overview of Web Development pitched at beginners.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/8sXRyHI3bLw?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;</description></item><item><title>Fly.io, Uptime Kuma &amp;amp; scraping a status page</title><link>https://devendevour.iankulin.com/fly-io-uptime-kuma-scraping-a-status-page/</link><pubDate>Fri, 02 Feb 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/fly-io-uptime-kuma-scraping-a-status-page/</guid><description>&lt;p&gt;&lt;a href="https://dribbble.com/shots/5657880-Fly-io-Logo" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/c1fef772e2dca5e1ab8c812f465c95a8.png" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been aware since I set up &lt;a href="https://devendevour.iankulin.com/uptime-kuma-nfty/"&gt;Uptime Kuma&lt;/a&gt; for my monitoring, that having an instance on my local network monitoring my VPS websites wasn&amp;rsquo;t ideal. The main reason being that the flakiest part of my infrastructure is my 4G home internet, so if that goes down I have no website monitoring, and even if I did, the notifications couldn&amp;rsquo;t get out.&lt;/p&gt;
&lt;p&gt;Of course, it would also be a simple matter to run an instance on the VPS that I host the sites on, but that has a similar problem in that if the VPS goes down, so does my monitoring of the VPS. What I really need is a third, independent space to run an instance.&lt;/p&gt;</description></item><item><title>How to Have Cooler File Icons in VS Code</title><link>https://devendevour.iankulin.com/how-to-have-cooler-file-icons-in-vs-code/</link><pubDate>Mon, 29 Jan 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/how-to-have-cooler-file-icons-in-vs-code/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/aliana8676_31_years_old_woman._she_is_digital_artist__graphic_d_196f8b49-ba40-458c-a8ed-54746fe109e7.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I watch a lot of programming demos on Youtube, and it&amp;rsquo;s been low key bugging me for a while that everyone has cooler little icons in the explorer view of their VS Code than I do. For example, they have the HTML 5 shield logo next to their &lt;code&gt;index.html&lt;/code&gt;, but I have the little fragment tag &amp;lt;&amp;gt;. Really, there was no point spending two hours customising my OhMyZSH! terminal if I&amp;rsquo;m just going to let myself down with disappointing VS Code file icons.&lt;/p&gt;</description></item><item><title>Getting Your Vite React App to Work on Github Pages</title><link>https://devendevour.iankulin.com/getting-your-vite-react-app-to-work-on-github-pages/</link><pubDate>Fri, 26 Jan 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/getting-your-vite-react-app-to-work-on-github-pages/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/combined.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;One of the many cool things about GitHub is &lt;a href="https://pages.github.com" target="_blank" rel="noopener"&gt;GitHub Pages&lt;/a&gt; - the free web hosting Microsoft gives you while they vacuum up &lt;a href="https://docs.github.com/en/copilot/overview-of-github-copilot/about-github-copilot-individual" target="_blank" rel="noopener"&gt;your code for CoPilot&lt;/a&gt; training. Each repository you keep there can have pages at &lt;code&gt;&amp;lt;your-github-username&amp;gt;.github.io/&amp;lt;repo-name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="github"&gt;GitHub&lt;/h3&gt; &lt;p&gt;To enable this, you need to go into the settings for the repository - look down the left for &amp;ldquo;Pages&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-12-31-at-1.58.05-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s possible to have it based on a complicated GitHub action (where your build step happens on GitHub when you push your code), but the easiest thing is just to have it deployed from a branch. To do this you choose which branch (usually main) and whereabouts in the main branch your HTML is. The choices are in the root of your project, or in the &lt;code&gt;/docs&lt;/code&gt; directory. I&amp;rsquo;ve chosen the &lt;code&gt;/docs&lt;/code&gt; directory in the screenshot above, since my messy React project is in the root.&lt;/p&gt;</description></item><item><title>React Expense Tracker App</title><link>https://devendevour.iankulin.com/react-expense-tracker-app/</link><pubDate>Mon, 22 Jan 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/react-expense-tracker-app/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/moneycoachjen_76363_woman_representing_money_2f84bb41-99db-4cb8-bc07-78e759e8b1f9.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m focused on React frontend skills these holidays, and &lt;a href="https://codewithmosh.com/p/ultimate-react-part1" target="_blank" rel="noopener"&gt;working through Mosh&amp;rsquo;s React 18&lt;/a&gt; course. The exercise today (which I think I nailed, although I spent more than the recommended hour on) was a small app to track expenses. Like most of Mosh&amp;rsquo;s exercises it was great because it exercised all the understandings up to that point - so it&amp;rsquo;s a good starting React project. It used Zod for the form validation which is completely new to me, but looks great.&lt;/p&gt;</description></item><item><title>What's unfinished in your Udemy?</title><link>https://devendevour.iankulin.com/whats-unfinished-in-your-udemy/</link><pubDate>Fri, 19 Jan 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/whats-unfinished-in-your-udemy/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pauchi0195_unfinished_robotic_bodies_female_scientist_rebel_bio_b9b95c92-d4af-4600-9c5b-e0974f6c2b18.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;If you work or study in tech, I always feel a good getting-to-know-you question is &amp;ldquo;what courses or tutorials did you start, but not finish?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;My Udemy doesn&amp;rsquo;t look &lt;em&gt;too&lt;/em&gt; bad:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-12-29-at-1.30.02-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The ZTM course was good, but I got stuck on an AI API exercise. I think it&amp;rsquo;s a common sticking point for students since Andrei includes a little rant about how it definitely does work - but I downloaded his repo with the solution and it was having the same errors I was and I gave up in frustration. I probably should have just skipped that one.&lt;/p&gt;</description></item><item><title>Copying Objects in JS</title><link>https://devendevour.iankulin.com/copying-objects-in-js/</link><pubDate>Mon, 15 Jan 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/copying-objects-in-js/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/nullgrim_three_ominous_mechanical_cyber_demon_sisters_asajj_ven_1eaa87e3-99a8-4c4a-9bc0-ec8fb807caad.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve paid for a month of Mosh to do his &lt;a href="https://codewithmosh.com/p/ultimate-react-part1" target="_blank" rel="noopener"&gt;React 18 course&lt;/a&gt; , and one of the things he makes a big deal about is not to go too deep with nested objects for your state. As soon as you start to update them it becomes apparent why.&lt;/p&gt;
&lt;p&gt;Because of the way state works in React, if we need to update part of an object it has to be deep copied, the changes applied to this copy, then that new copy passed back to React to replace the previous version. So, how we copy objects becomes a matter of particular interest.&lt;/p&gt;</description></item><item><title>CSS for React Components</title><link>https://devendevour.iankulin.com/css-for-react-components/</link><pubDate>Fri, 12 Jan 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/css-for-react-components/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-12-27-at-3.30.32-pm.jpg" alt="" class="img-responsive"&gt; 
&lt;em&gt;Subscribe to my UX design course 😉&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you think back to HTML as being a document with headings and paragraphs and other semantic bits, it made a lot of sense to have the styles (expressed as CSS) separate to the document. This allows us to change the styles without touching the document - perhaps the user wanted a dark theme, needed the text bigger for accessibility, or perhaps the document was being consumed in some other way - for example a screen reader - so the styles were superfluous.&lt;/p&gt;</description></item><item><title>React - a To Do Example</title><link>https://devendevour.iankulin.com/react-a-to-do-example/</link><pubDate>Mon, 08 Jan 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/react-a-to-do-example/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/crake_react_framework_logo_in_a_stylized_and_minimalist_ink-sta_cd004169-cd3c-4f76-8314-3d841f7233ec.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;m on a roll making different versions of the To Do app, this might be a good time to talk about &lt;a href="https://react.dev/" target="_blank" rel="noopener"&gt;React&lt;/a&gt; . React is one of the giants of front end libraries. It&amp;rsquo;s based on a few big ideas - and to work effectively in React you need to wrap your head around these.&lt;/p&gt;
&lt;h3 id="overview"&gt;Overview&lt;/h3&gt; &lt;p&gt;Components - when you are developing in React, the starting point of your build is to decompose the user interface in to logical pieces. These components (comprising a mixture of HTML and Javascript) will be the building blocks of your app. In a good composable architecture components are reusable, and that is true for React (there are several sources of components you can pull in). For example, if you created some sort of special slider for your app, it is possible to reuse that quite easily.&lt;/p&gt;</description></item><item><title>htmx - A To Do Example</title><link>https://devendevour.iankulin.com/htmx-a-to-do-example/</link><pubDate>Fri, 05 Jan 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/htmx-a-to-do-example/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/0-eawgkaegdkhvqwcg.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;HTMX is an interesting project to me, and I&amp;rsquo;ve used it a bit in my large collection of 70% completed side projects, but haven&amp;rsquo;t really discussed it here. The plan for this post is to talk briefly about what it is exactly, then convert a simple &amp;lsquo;conventional&amp;rsquo; (HTML/CSS/Javascript) app to htmx and think about some the differences.&lt;/p&gt;
&lt;h3 id="htmx"&gt;htmx&lt;/h3&gt; &lt;p&gt;You could (I recommend you do) read the &lt;a href="https://hypermedia.systems/book/contents/" target="_blank" rel="noopener"&gt;book&lt;/a&gt; about the concepts behind &lt;a href="https://htmx.org/" target="_blank" rel="noopener"&gt;htmx&lt;/a&gt; . Carson Gross (the man behind htmx) calls it a book, but its quite the treatise, it could fairly be called a manifesto.&lt;/p&gt;</description></item><item><title>Testing Node.js apps - Mocha, Chai, and Supertest</title><link>https://devendevour.iankulin.com/testing-node-js-apps-mocha-chai-and-supertest/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/testing-node-js-apps-mocha-chai-and-supertest/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/marinashideroff_abstract_monitoring_internet_station_surrounded_9f91ec3e-e8c0-4567-bee6-0315eb2375f0.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Bruno is a great open source Postman/Insomnia replacement, and I&amp;rsquo;ve been using it for basic tests of my node servers using the built in asserts and loving it. This is pretty great, and I gather it&amp;rsquo;s also possible to go beyond this and &lt;a href="https://docs.usebruno.com/testing/introduction.html" target="_blank" rel="noopener"&gt;write tests in JS in Bruno&lt;/a&gt; . I believe it also has the hooks needed to build it into your CI/CD systems.&lt;/p&gt;
&lt;p&gt;Any large project is probably going to benefit from a more comprehensive suit of testing tools, and while I&amp;rsquo;ll still be using Bruno, my serious tests will be managed with these other tools.&lt;/p&gt;</description></item><item><title>Simple SQLite in Express</title><link>https://devendevour.iankulin.com/simple-sqlite-in-express/</link><pubDate>Thu, 28 Dec 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/simple-sqlite-in-express/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/shmbo_an_artificial_intelligence_entitys_head_embodying_the_ess_f348db7a-e7b6-4620-beda-44fdb8e565d3.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have experience with &lt;a href="https://www.sqlite.org/index.html" target="_blank" rel="noopener"&gt;SQLite&lt;/a&gt; and want to shift one of my apps over from Mongoose since apparently SQLite is &lt;a href="https://www.sqlite.org/whentouse.html" target="_blank" rel="noopener"&gt;much more capable&lt;/a&gt; than I imagined. My usual tactic when trying something new is to try and get a minimal project working on it, so what follows is the simplest possible node/express REST API to demo SQLite.&lt;/p&gt;
&lt;p&gt;The simplest possible Express app is going to look something like this. Of course we would have gone to the terminal with &lt;code&gt;npm i express&lt;/code&gt; first so this could run.&lt;/p&gt;</description></item><item><title>Using LXC templates in Proxmox</title><link>https://devendevour.iankulin.com/using-lxc-templates-in-proxmox/</link><pubDate>Sun, 24 Dec 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/using-lxc-templates-in-proxmox/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/unagi911_identical_female_triplets_sit_in_three_large_silver_do_d51d8006-cd33-4934-b7ab-988aecc5da7d.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I wrote a couple of weeks ago about a &lt;a href="https://devendevour.iankulin.com/new-self-hosted-service-workflow/"&gt;standard workflow&lt;/a&gt; I use to spin up a web service in an LXC container to add to my self-hosted collection of services. It went a bit like: do this, and then this, then this other thing. Whenever you find yourself repeating a set of steps like this, it&amp;rsquo;s usually a sign that you should be automating it. Not just to save time (although this is a key benefit) but also to improve repeatability and to avoid introducing errors.&lt;/p&gt;</description></item><item><title>Practice your restore strategy</title><link>https://devendevour.iankulin.com/practice-your-restore-strategy/</link><pubDate>Thu, 21 Dec 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/practice-your-restore-strategy/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_7342.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;My homelab set up is a production node, (pve-prod1) a backup production node (pve-prod2) and a development machine (pve-dev1). They are all G2 800 minis, but pve-prod1 has a i7 6700T and 32GB RAM, where as the other two are i5 6500T with 16GB. My thinking is that the older two can easily share the workload of the main production machine for disaster recovery. Everything is virtualised on top of Proxmox, so sharing up the VM&amp;rsquo;s and containers is trivial.&lt;/p&gt;</description></item><item><title>Gogs, Gitea, Forgejo</title><link>https://devendevour.iankulin.com/gogs-gitea-forgejo/</link><pubDate>Mon, 18 Dec 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/gogs-gitea-forgejo/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_7071-1.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been really pleased with &lt;a href="https://devendevour.iankulin.com/tags/gogs/"&gt;Gogs&lt;/a&gt; - it&amp;rsquo;s lightweight, was simple to spin up, and has worked perfectly. But then this morning on Mastodon, there&amp;rsquo;s a &lt;a href="https://mastodon.social/@Codeberg@social.anoxinon.de/111471407276450348" target="_blank" rel="noopener"&gt;post from @Codeberg.org&lt;/a&gt; describing a security vulnerability in their Git hosting project Forgejo. This issue also apparently affects Gitea and Gogs - what&amp;rsquo;s up with that?&lt;/p&gt;
&lt;p&gt;I actually already did spend a bit of time comparing Gogs and Gitea before deciding on Gogs, since I&amp;rsquo;d heard of people running Gitea over the past year or so, but only seen that Gogs seemed to be popular with self-hosters in a Lemmy post I&amp;rsquo;d read. My first impression was that Gitea was more focused on CI/CD and seemed to have a more complicated install process.&lt;/p&gt;</description></item><item><title>Git - pushing to two remotes</title><link>https://devendevour.iankulin.com/git-pushing-to-two-remotes/</link><pubDate>Fri, 15 Dec 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/git-pushing-to-two-remotes/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/tanjian1998_an_ai_humanoid_pushing_a_shopping_cart_with_that_ha_5eceff04-704f-403d-af6d-46fd9ba57909.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I am loving running a local Gogs instance - it&amp;rsquo;s nice pushing my git repos to a totally private hub that I know is backed up with all my other self-hosted infrastructure.&lt;/p&gt;
&lt;p&gt;Of course, there&amp;rsquo;s good reasons to have code in GitHub as well - my build-in-public philosophy, the vague possibility that some of it might be useful to someone, my contribution to our future AI overlords, and when I need to make some code linkable - for example from one of these posts. And of course there&amp;rsquo;s this bit of social-engineering which I assume was inspired by the bathroom decor in &lt;a href="https://i.pinimg.com/originals/94/23/85/9423854153f55938c454a061ad5462fe.gif" target="_blank" rel="noopener"&gt;Veronica Mars&lt;/a&gt; .&lt;/p&gt;</description></item><item><title>Concurrency and channels in Go</title><link>https://devendevour.iankulin.com/concurrency-and-channels-in-go/</link><pubDate>Tue, 12 Dec 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/concurrency-and-channels-in-go/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/portal-logo.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;In the long ago times, I&amp;rsquo;d done several years of commercial programming before I ever had to worry about dealing with multiple things happening at the same time. Perhaps because of the rarity of this problem, doing it in traditional languages was not always elegant.&lt;/p&gt;
&lt;p&gt;In the modern world of everything happening on the network, and systems being build out of micro-services and APIs, the beginning programmer probably has to deal with this stuff in Programming 102. Luckily, modern languages have these considerations built in, and one language with a particular reputation for that is Go.&lt;/p&gt;</description></item><item><title>Date formatting in Go is quirky</title><link>https://devendevour.iankulin.com/date-formatting-in-go-is-quirky/</link><pubDate>Sat, 09 Dec 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/date-formatting-in-go-is-quirky/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/the_og_jay_go_programming_language_gopher_avatar._the_environme_3dc4f7dc-43b2-459e-8b76-57d9771eb9f7.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;When I&amp;rsquo;m working in an unfamiliar language, I find its quicker to just ask ChatGPT to write samples of anything I need than to look it up. For instance, last night I needed to format a date in Go, and rather than Google that and pick one of the results and scroll past the ads to read something, I just asked ChatGPT to give me a code example of formatting a date I gave it to DDMMYYYY.&lt;/p&gt;</description></item><item><title>Gogs - your own tiny GitHub</title><link>https://devendevour.iankulin.com/gogs-your-own-tiny-github/</link><pubDate>Wed, 06 Dec 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/gogs-your-own-tiny-github/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-11-20-at-8.08.37-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;(edit: - I&amp;rsquo;ve &lt;a href="https://devendevour.iankulin.com/gogs-gitea-forgejo/"&gt;had a rethink about&lt;/a&gt; my source hosting)&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;re familiar with coding tools, like the excellent &lt;a href="https://code.visualstudio.com/" target="_blank" rel="noopener"&gt;VS Code&lt;/a&gt; , and &lt;a href="https://git-scm.com/docs/git" target="_blank" rel="noopener"&gt;git&lt;/a&gt; , it&amp;rsquo;s immediately apparent that these tools can be applicable for other purposes. A great example is that I now do my financial accounting in plain text (using &lt;a href="https://github.com/beancount/beancount" target="_blank" rel="noopener"&gt;beancount&lt;/a&gt; ). I have a python script that converts by bank account data in to the beancount format text files, I edit them in VS Code with a &lt;a href="https://marketplace.visualstudio.com/items?itemName=Lencerf.beancount" target="_blank" rel="noopener"&gt;plugin&lt;/a&gt; that does the syntax highlighting and checks everything balances.&lt;/p&gt;</description></item><item><title>New Self-Hosted Service Workflow</title><link>https://devendevour.iankulin.com/new-self-hosted-service-workflow/</link><pubDate>Sun, 03 Dec 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/new-self-hosted-service-workflow/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/es047_illustration_of_a_workflow_with_only_four_text_boxes_with_b026526e-30b7-45c7-9491-080adc1594ce.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve developed a bit of a workflow for setting up a new service of some type on the homelab. Installing it is the obvious thing, but I also have a few quality of life things I do to make it a full production-quality part of my installation. I thought it might be helpful to run through those things using a recent example of adding &lt;a href="https://www.audiobookshelf.org/" target="_blank" rel="noopener"&gt;audiobookshelf&lt;/a&gt; .&lt;/p&gt;
&lt;h3 id="audiobookshelf"&gt;audiobookshelf&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.audiobookshelf.org/" target="_blank" rel="noopener"&gt;audiobookshelf&lt;/a&gt; is a web based system for viewing, playing, downloading and/or generally managing your audio books. I&amp;rsquo;ve been an &lt;a href="https://www.audible.com.au/" target="_blank" rel="noopener"&gt;Audible&lt;/a&gt; user/subscriber, but recently got grumpy at them about something - I think I had paused my subscription, and my downloaded books were still available on my phone. I was halfway through one, upgraded the app, and then wasn&amp;rsquo;t able to play the book without re-subscribing. That might not be exactly right, but it was some type of frustrating carry on like that.&lt;/p&gt;</description></item><item><title>Ansible - Importing a Playbook</title><link>https://devendevour.iankulin.com/ansible-importing-a-playbook/</link><pubDate>Thu, 30 Nov 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ansible-importing-a-playbook/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/billyoblivion_intricate_and_highly_detailed_portable_ansible_la_c7e1c515-a2e6-4fef-b3c5-2d35e04ba09e.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="https://devendevour.iankulin.com/tags/ansible/"&gt;Ansible&lt;/a&gt; is a system for automating server tasks, and these tasks are written in a special yaml file called a playbook. I had need to call one playbook from another today and learned a couple of things.&lt;/p&gt;
&lt;h3 id="plays-vs-tasks"&gt;Plays vs Tasks&lt;/h3&gt; &lt;p&gt;In Ansible we run &lt;em&gt;tasks&lt;/em&gt;. A group of tasks run against one particular sets of hosts is called a &lt;em&gt;play&lt;/em&gt;. Here is a playbook with one play, and two tasks:&lt;/p&gt;</description></item><item><title>ViewTube</title><link>https://devendevour.iankulin.com/viewtube/</link><pubDate>Mon, 27 Nov 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/viewtube/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-11-18-at-5.17.47-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Whenever I encounter one of those &amp;ldquo;What are you self-hosting?&amp;rdquo; threads, I know I&amp;rsquo;m about to waste an hour looking at, and often trying out, software I probably don&amp;rsquo;t really need, and that was the case with &lt;a href="https://lemmy.world/post/8385160" target="_blank" rel="noopener"&gt;this post&lt;/a&gt; on the &lt;a href="https://lemmy.world/c/selfhost@lemmy.ml" target="_blank" rel="noopener"&gt;lemmy.world Selfhosted&lt;/a&gt; community.&lt;/p&gt;
&lt;p&gt;The basic idea of ViewTube is that it&amp;rsquo;s a self-hosted front end for YouTube, which just happens to strip out all the advertising and tracking. You can create your own local accounts which allows you to subscribe to channels and which keeps your progress so you don&amp;rsquo;t start over if you go back to a video - although I couldn&amp;rsquo;t see a history list. Forgetting your history might be a feature in an app designed to prevent tracking.&lt;/p&gt;</description></item><item><title>Adding Front Matter To mdserver</title><link>https://devendevour.iankulin.com/adding-front-matter-to-mdserver/</link><pubDate>Fri, 24 Nov 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/adding-front-matter-to-mdserver/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/brobinhook_sketch_design_of_a_modern_landing_page_for_a_webdev__79beff03-b181-4195-90b9-ff9c41b9f138.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The very first issue I opened on &lt;a href="https://devendevour.iankulin.com/displaying-markdown-as-html/"&gt;mdserver&lt;/a&gt; - my server project that serves HTML from markdown files - was that the title of the page (which shows in the browser tab, and is used for browser bookmarks) needed to be set &lt;em&gt;inside&lt;/em&gt; the markdown file, rather than generated from the file name. I didn&amp;rsquo;t invent this idea - I&amp;rsquo;ve seen this sort of metadata in the top of Jekyll and Hugo markdown. Here&amp;rsquo;s an example from the &lt;a href="https://jekyllrb.com/docs/front-matter/" target="_blank" rel="noopener"&gt;Jekyll website&lt;/a&gt; :&lt;/p&gt;</description></item><item><title>Building Docker images for multiple architectures</title><link>https://devendevour.iankulin.com/building-docker-images-for-multiple-architectures/</link><pubDate>Mon, 20 Nov 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/building-docker-images-for-multiple-architectures/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/featured-image-shipping-containers.jpeg.webp" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;My little mdserver app has been a good way for me to start experimenting with the the devops side of things, especially building for Docker. Since I wanted to make the Docker image available for ARM Linux &amp;amp; x86 Linux I had a janky shell script that looked like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/bin/bash

# Extract the version number from package.json using jq
VERSION=$(jq -r .version package.json)

docker build --platform linux/amd64 -t iankulin/mdserver:$VERSION -t iankulin/mdserver:latest .
docker build --platform linux/arm64 -t iankulin/mdserver:arm64-$VERSION -t iankulin/mdserver:arm64-latest .

docker push iankulin/mdserver:arm64-$VERSION 
docker push iankulin/mdserver:arm64-latest 

docker push iankulin/mdserver:$VERSION
docker push iankulin/mdserver:latest 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So I&amp;rsquo;d build two different versions, and use the tags to separate them. In the registry it&amp;rsquo;d look like this:&lt;/p&gt;</description></item><item><title>Docker volume backup is more complicated than it should be</title><link>https://devendevour.iankulin.com/docker-volume-backup-is-more-complicated-than-it-should-be/</link><pubDate>Fri, 17 Nov 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/docker-volume-backup-is-more-complicated-than-it-should-be/</guid><description>&lt;p&gt;&lt;a href="https://unccelearn.org/course/view.php?id=128&amp;amp;page=overview&amp;amp;lang=en" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/big.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;When I set up my first Docker container (I think for &lt;a href="https://devendevour.iankulin.com/uptime-kuma-nfty/"&gt;Uptime Kuma&lt;/a&gt; ), I had read around and understood there were two choices for persistent; &lt;em&gt;bind mounts&lt;/em&gt; (where the data inside the container is effectively a symlink to a location on the local file system) or &lt;em&gt;name volumes&lt;/em&gt; where Docker abstracted that away a bit, so you didn&amp;rsquo;t have to worry where it was - I sort of understood Docker &amp;lsquo;managed&amp;rsquo; it.&lt;/p&gt;</description></item><item><title>Bruno asserts</title><link>https://devendevour.iankulin.com/bruno-asserts/</link><pubDate>Sat, 11 Nov 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/bruno-asserts/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-10-22-at-12.11.09-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I mentioned &lt;a href="https://www.usebruno.com/" target="_blank" rel="noopener"&gt;Bruno&lt;/a&gt; the other day. Although it&amp;rsquo;s still very much under development, it is shaping up as a great Postman/Insomnia replacement.&lt;/p&gt;
&lt;p&gt;One of the aspects I&amp;rsquo;ve been using today is asserts. As part of a request, you can add some asserts - so when you&amp;rsquo;re hitting an endpoint it will check what status should it be returning, or given the data you&amp;rsquo;re passing in, what should be in the response body.&lt;/p&gt;</description></item><item><title>Displaying markdown as HTML</title><link>https://devendevour.iankulin.com/displaying-markdown-as-html/</link><pubDate>Wed, 08 Nov 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/displaying-markdown-as-html/</guid><description>&lt;p&gt;In the spirit of over-complicating things, when I wanted to collect all the links to the services on my homelab into one place, I decided I needed to write them in markdown, and have them converted on the fly into HTML by a server. Then when I couldn&amp;rsquo;t find exactly what I was after (&lt;a href="http://harpjs.com/" target="_blank" rel="noopener"&gt;Harp&lt;/a&gt; was closest) of course, I decided to write it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/distracted.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;h3 id="markdown"&gt;Markdown&lt;/h3&gt; &lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Markdown" target="_blank" rel="noopener"&gt;Markdown&lt;/a&gt; has definitely been having it&amp;rsquo;s moment over the last couple of years. It&amp;rsquo;s a simple open format mark-up language that is quite readable in it&amp;rsquo;s source form. Although it&amp;rsquo;s now very fashionable as an input for static site generators, most people will have run in to it when adding simple formatting to forum comments or on instant messaging platforms.&lt;/p&gt;</description></item><item><title>Ansible playbook to start Proxmox hosts</title><link>https://devendevour.iankulin.com/ansible-playbook-to-start-proxmox-hosts/</link><pubDate>Sun, 05 Nov 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ansible-playbook-to-start-proxmox-hosts/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/mick-jagger-start-me-up-video-the-rolling-stones-far-out-magazine-copy.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="https://devendevour.iankulin.com/proxmox-tags-to-solve-a-problem/"&gt;In my last post&lt;/a&gt; , I talked about tagging guests in a Proxmox node so I could easily see which VMs and LXCs I needed to manually start before I ran an Ansible script to run all my &lt;code&gt;apt updates&lt;/code&gt;. It would have been reasonable to wonder why I didn&amp;rsquo;t just add things to my playbook to magically do that.&lt;/p&gt;
&lt;p&gt;The answer would be, I haven&amp;rsquo;t gotten around to it yet, so here goes:&lt;/p&gt;</description></item><item><title>Proxmox tags to solve a problem</title><link>https://devendevour.iankulin.com/proxmox-tags-to-solve-a-problem/</link><pubDate>Thu, 02 Nov 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/proxmox-tags-to-solve-a-problem/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/slacroix_save_bookmark_flat_icon_vector_online_single_social_me_113006e0-eb8e-4cff-8692-20eb0573f35d.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Each weekend I run an Ansible script that updates all my apt based VMs and containers. For the production machines, that&amp;rsquo;s everything, but my dev Proxmox is full of half-finished projects. Some of these have IP addresses reserved and are in the Ansible hosts file (because whatever service they are running is almost ready to move to the production server) others do not.&lt;/p&gt;
&lt;p&gt;Long story short, the dev server has some containers and VM&amp;rsquo;s that need turned on before I run the updates, and some that don&amp;rsquo;t. I could just start them all up, for the ten minutes the updates usually take, but that seems wasteful somehow. If there was only some way to mark the ones I need to turn on in the Proxmox webgui! Well, there is. We can add tags to machines in Proxmox.&lt;/p&gt;</description></item><item><title>apt update - BADSIG 871920D1991BC93C</title><link>https://devendevour.iankulin.com/apt-update-badsig-871920d1991bc93c/</link><pubDate>Mon, 30 Oct 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/apt-update-badsig-871920d1991bc93c/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/thdgown_there_was_a_huge_dragon_guarding_the_treasure_in_the_wo_5bbc5295-9c5c-4e04-805a-912552832900.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I have an ansible script that runs each weekend which basically does an &lt;code&gt;apt update &amp;amp;&amp;amp; apt upgrade -Y&lt;/code&gt; on every Debian based instance. This weekend it failed on one Ubuntu host. When I went it to try it manually, this was the output:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Hit:1 http://au.archive.ubuntu.com/ubuntu jammy InRelease
Hit:2 https://download.docker.com/linux/ubuntu jammy InRelease 
Hit:3 http://au.archive.ubuntu.com/ubuntu jammy-backports InRelease 
Hit:4 http://au.archive.ubuntu.com/ubuntu jammy-security InRelease 
Get:5 http://au.archive.ubuntu.com/ubuntu jammy-updates InRelease [119 kB] 
Err:5 http://au.archive.ubuntu.com/ubuntu jammy-updates InRelease 
 The following signatures were invalid: BADSIG 871920D1991BC93C Ubuntu Archive Automatic Signing Key (2018) &amp;lt;ftpmaster@ubuntu.com&amp;gt;
Get:6 https://pkgs.tailscale.com/stable/ubuntu jammy InRelease
Fetched 125 kB in 1s (125 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
11 packages can be upgraded. Run &amp;#39;apt list --upgradable&amp;#39; to see them.
W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: http://au.archive.ubuntu.com/ubuntu jammy-updates InRelease: The following signatures were invalid: BADSIG 871920D1991BC93C Ubuntu Archive Automatic Signing Key (2018) &amp;lt;ftpmaster@ubuntu.com&amp;gt;
W: Failed to fetch http://au.archive.ubuntu.com/ubuntu/dists/jammy-updates/InRelease The following signatures were invalid: BADSIG 871920D1991BC93C Ubuntu Archive Automatic Signing Key (2018) &amp;lt;ftpmaster@ubuntu.com&amp;gt;
W: Some index files failed to download. They have been ignored, or old ones used instead.
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="solved"&gt;Solved&lt;/h3&gt; &lt;p&gt;The first &lt;a href="https://ubuntuforums.org/showthread.php?t=2484710" target="_blank" rel="noopener"&gt;google result&lt;/a&gt; mentions apt-cache - which &lt;a href="https://devendevour.iankulin.com/caching-apt-updates/"&gt;I also run&lt;/a&gt; , so a first level debug step is to delete the &lt;code&gt;/etc/apt/apt.conf.d/00aptproxy&lt;/code&gt; file that redirects apt requests to the cache I run in an LXC container. After that, if I re-run the &lt;code&gt;apt update&lt;/code&gt; it works perfectly. Seems like a problem with the cache then. I&amp;rsquo;m not sure why it would only affect this host though - I have other Ubuntu VM&amp;rsquo;s in the fleet that are not getting the original error.&lt;/p&gt;</description></item><item><title>We need to talk about Bruno</title><link>https://devendevour.iankulin.com/we-need-to-talk-about-bruno/</link><pubDate>Fri, 27 Oct 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/we-need-to-talk-about-bruno/</guid><description>&lt;p&gt;&lt;a href="https://www.usebruno.com/" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-10-01-at-6.01.17-pm.png" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve &lt;a href="https://devendevour.iankulin.com/how-to-deploy-a-node-js-app/"&gt;mentioned before&lt;/a&gt; that I was using Insomnia as a tool to check my REST APIs as I was developing them, and that I was avoiding Postman (which I guess is more widely used since it&amp;rsquo;s worth &lt;a href="https://techcrunch.com/2021/08/18/api-platform-postman-valued-at-5-6-billion-in-225-million-fundraise/" target="_blank" rel="noopener"&gt;USD5.6 billion&lt;/a&gt; ) because&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The only reason I&amp;rsquo;m using Insomnia instead of Postman is that when I tried Postman, it straight away wanted some of my data to make it work. Insomnia hasn&amp;rsquo;t forced me to do that yet.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Tailscale keys expire</title><link>https://devendevour.iankulin.com/tailscale-keys-expire/</link><pubDate>Tue, 24 Oct 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/tailscale-keys-expire/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/lea_liu_a_anime_mermaid_inside_a_sci-fi_mermaid_generator_merma_c06e5a6c-7444-49aa-9312-e1ba117aba7c.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I have an &lt;a href="https://devendevour.iankulin.com/ansible-with-secrets/"&gt;Ansible playbook&lt;/a&gt; I run each weekend to do all the &lt;code&gt;apt&lt;/code&gt; updates. As well as keeping everything up to date, it&amp;rsquo;s a good check-in that everything&amp;rsquo;s alive and working as expected. I have Uptime Kuma checking the services are alive, and that no one is running out of disk or memory so there shouldn&amp;rsquo;t be any drama right?&lt;/p&gt;
&lt;p&gt;This weekend, three instances (two remote, one local) timed out with &amp;ldquo;unreachable&amp;rdquo;.&lt;/p&gt;</description></item><item><title>New Project Routine</title><link>https://devendevour.iankulin.com/new-project-routine/</link><pubDate>Sat, 21 Oct 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/new-project-routine/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/koda702_create_a_detailed_and_visually_engaging_collage_highlig_23cd7276-3e92-46ca-a055-086e4ff35417.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I have a sort of muscle memory for starting little web projects now. I seem to have landed on node/express SSR apps with HTMX sprinkles. So it goes a bit like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a working directory - all lower case with a simple, but unlikely to be duplicated by me, name.&lt;/li&gt;
&lt;li&gt;Open the directory in vscode&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm init&lt;/code&gt; in the directory to create the &lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;create a &lt;code&gt;public&lt;/code&gt; sub directory, and drop &lt;a href="https://htmx.org/docs/#installing" target="_blank" rel="noopener"&gt;&lt;code&gt;htmx.min.js&lt;/code&gt;&lt;/a&gt; in there, and create a &lt;code&gt;styles.css&lt;/code&gt; there. I&amp;rsquo;m always conflicted about what to do about this htmx dependency. I&amp;rsquo;d rather host it rather than use their CDN because &lt;a href="https://blog.wesleyac.com/posts/why-not-javascript-cdn" target="_blank" rel="noopener"&gt;reasons&lt;/a&gt; . But I also feel bad about committing it on Github. I could .gitignore it, but then when I clone the project on the production server I&amp;rsquo;d need to add another step to download it. HTMX is only 44K, and Microsoft can afford the bandwidth, so for the moment I commit them, but I need a better solution for the future.&lt;/li&gt;
&lt;li&gt;using the git tools in vscode, add &lt;code&gt;.DS_Store&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt; (which also creates it), then edit it to also ignore &lt;code&gt;node_modules&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm install express&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm install ejs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;create a server.js, and add the &lt;a href="https://nodejs.org/en/docs/guides/getting-started-guide" target="_blank" rel="noopener"&gt;hello world&lt;/a&gt; code&lt;/li&gt;
&lt;li&gt;create a &lt;code&gt;readme.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;commit these files as &amp;ldquo;initial&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Create the repo on github with the same name - no readme and no licence. I do it this way for a couple of reasons - I want to find out at this point if I&amp;rsquo;ve already used this repo name, and I want it to give me the cut and paste commands to push the repository.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-09-25-at-9.55.46-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;</description></item><item><title>Getting Tailscale working in LXC containers</title><link>https://devendevour.iankulin.com/getting-tailscale-working-in-lxc-containers/</link><pubDate>Wed, 18 Oct 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/getting-tailscale-working-in-lxc-containers/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/stoneyhawk_wireguard_mesh_network_9cc1d03b-813c-433e-9af6-4e92ba6f6783.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve taken to running lots of my services in LXC containers under Proxmox. I like the feeling of installing in a VM, but it&amp;rsquo;s lightweight. I like the backups, I like things being isolated from each other, I like moving them around between machines easily. I&amp;rsquo;m just a big LXC lover at the moment.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also a Tailscale lover, and the generous number of nodes in the free tier means I now just routinely install them in my VMs and containers without a thought.&lt;/p&gt;</description></item><item><title>Certbot - adding more virtual hosts</title><link>https://devendevour.iankulin.com/certbot-adding-more-virtual-hosts/</link><pubDate>Sun, 15 Oct 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/certbot-adding-more-virtual-hosts/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/dangling_pointer._a_central_neural_network_bathed_in_teal_and_m_9563eacf-6a8a-481d-a9e5-7fa72cabb4ea.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got a domain that&amp;rsquo;s not currently used, so I&amp;rsquo;m going to set it up as a virtual host under NGINX. This server is already serving two domains set up with Certbot for SSL. Is it going to be possible to add another site and have Certbot manage the certificates for it after I&amp;rsquo;ve run Certbot once?&lt;/p&gt;
&lt;p&gt;When I googled around to find out, I didn&amp;rsquo;t find anything - which is usually a sign I&amp;rsquo;m either asking a wrong question, or it&amp;rsquo;s so little drama that no one ever mentions it. I decided just to move the site, check it was all working for the http version, then run Certbot and see what it said.&lt;/p&gt;</description></item><item><title>Certbot &amp;amp; Let's Encrypt are great</title><link>https://devendevour.iankulin.com/certbot-lets-encrypt-are-great/</link><pubDate>Thu, 12 Oct 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/certbot-lets-encrypt-are-great/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/certbot.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been managing SSL certificates for my domains purchased from &lt;a href="https://porkbun.com/" target="_blank" rel="noopener"&gt;PorkBun&lt;/a&gt; by going there every 90 days downloading the certificates, &lt;a href="https://devendevour.iankulin.com/installing-ssl-certificates-with-nginx-on-docker/"&gt;joining them together&lt;/a&gt; to make the &lt;code&gt;fullchain.pem&lt;/code&gt; then &lt;code&gt;scp&lt;/code&gt;-ing them to my servers. That&amp;rsquo;s been sort of manageable, but less than ideal.&lt;/p&gt;
&lt;p&gt;It also doesn&amp;rsquo;t work for my Australian domains. Since there&amp;rsquo;s strict rules about who can own a domain in the &lt;code&gt;.au&lt;/code&gt; space (&lt;em&gt;you have to have some sort of right to the name - a random person can&amp;rsquo;t obtain the &lt;code&gt;coke.com.au&lt;/code&gt; domain unless that&amp;rsquo;s a trading name, a trademark, or something similar&lt;/em&gt;), they have to be managed by one of about eight organisations, and the offerings are much simpler.&lt;/p&gt;</description></item><item><title>BOINC in an LXC container</title><link>https://devendevour.iankulin.com/boinc-in-an-lxc-container/</link><pubDate>Mon, 09 Oct 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/boinc-in-an-lxc-container/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/boinc_logo.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Years ago, I was very keen on the &lt;a href="https://youtu.be/WwxTc6pFOcU" target="_blank" rel="noopener"&gt;SETI@home&lt;/a&gt; project that used a distributed computing model whereby packets of digitized received radio data were farmed out to individuals&amp;rsquo; computers to be processed to look for any unusual signals that could potentially be from an intelligent extra-terrestrial source.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s long since defunct, but the idea lives on with &lt;a href="https://boinc.berkeley.edu/" target="_blank" rel="noopener"&gt;BOINC&lt;/a&gt; - a system run out of Berkley that allows different science organisations to offer projects to run on individuals&amp;rsquo; computers.&lt;/p&gt;</description></item><item><title>Solved DNS Issues - Proxmox, LXC, Ubuntu, Tailscale</title><link>https://devendevour.iankulin.com/solved-dns-issues-proxmox-lxc-ubuntu-tailscale/</link><pubDate>Fri, 06 Oct 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/solved-dns-issues-proxmox-lxc-ubuntu-tailscale/</guid><description>&lt;p&gt;&lt;a href="https://i.imgur.com/WmRbmf5.png" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/wmrbmf5.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve picked up an new TP-Link WAP with Omada, so I wanted to spin up an Ubuntu 20.04 LXC to run the controller software in, and ended up spending a couple of hours figuring out why things where not working.&lt;/p&gt;
&lt;p&gt;The initial problem was I was having connectivity issues pulling down the updates for all the packages required. I went down a bit of a tangent because I installed an apt cache the other day, so I was looking for problems there. Eventually I narrowed it down to DNS not working and started A/B testing like this:&lt;/p&gt;</description></item><item><title>Caching APT updates</title><link>https://devendevour.iankulin.com/caching-apt-updates/</link><pubDate>Tue, 03 Oct 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/caching-apt-updates/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/quangpham2576_realistic_red_hen_that_is_serving_a_plate_of_soft_b56bccf5-82c1-4bf9-9936-edd7606ab70a.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s bothered me for a while that all these VM&amp;rsquo;s are pulling down a lot of the same updates. As well as needlessly using some bandwidth, I&amp;rsquo;m hammering the update servers (that I don&amp;rsquo;t pay for) with the same requests over and over. I did briefly consider running my own mirror, but that&amp;rsquo;s not simple, plus I&amp;rsquo;d then be mirroring a heap of files in a complete repository that I&amp;rsquo;d never use. What I really needed was some sort of cache so once I&amp;rsquo;ll pulled down an update, it would hang around for a few days being available to other machines on the local network. Luckily, that exact thing exists - &lt;a href="https://www.unix-ag.uni-kl.de/~bloch/acng/html/index.html" target="_blank" rel="noopener"&gt;APT Cacher NG&lt;/a&gt; .&lt;/p&gt;</description></item><item><title>Installing service with Ansible</title><link>https://devendevour.iankulin.com/installing-service-with-ansible/</link><pubDate>Sat, 30 Sep 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/installing-service-with-ansible/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/mlguy_synthetic_woman_is_installing_her_robotic_arm_ac961357-5997-4b2a-9b50-6f91ae9a4bf7.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Having written my little monitoring endpoint in Go, it needs pushed out to all my servers and VM&amp;rsquo;s. Clearly this is a job for Ansible which I&amp;rsquo;ve already &lt;a href="https://devendevour.iankulin.com/ansible-with-secrets/"&gt;dabbled my toes in&lt;/a&gt; . Before we get onto doing that though, we need to have a think about how to make it a service.&lt;/p&gt;
&lt;h3 id="linux-services"&gt;Linux Services&lt;/h3&gt; &lt;p&gt;A service in Linux is just a program, but one that&amp;rsquo;s usually required to be running all the time to provide some piece of functionality. The &amp;ldquo;program&amp;rdquo; can be any executable, but to allow systemd to manage it, we need to tell it a bit about what we want in a &lt;code&gt;.service&lt;/code&gt; file. This file is used by &lt;code&gt;systemd&lt;/code&gt; to know how to manage the service. They can get quite complex, but here&amp;rsquo;s the simple one for &lt;code&gt;vitals-glimpse&lt;/code&gt; - my little monitoring API endpoint.&lt;/p&gt;</description></item><item><title>Simple API endpoint in Go</title><link>https://devendevour.iankulin.com/simple-api-endpoint-in-go/</link><pubDate>Wed, 27 Sep 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/simple-api-endpoint-in-go/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/gopher.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like a small, quick, low load endpoint on all my nodes and VM&amp;rsquo;s that exposes a text keyword indicating if that machine is okay for RAM and disk space. I&amp;rsquo;m currently using &lt;a href="https://devendevour.iankulin.com/tags/uptime-kuma/"&gt;Uptime Kuma&lt;/a&gt; to monitor if these machines are pingable, but I&amp;rsquo;d love a tiny bit more information from them so I&amp;rsquo;d get a &lt;a href="https://devendevour.iankulin.com/uptime-kuma-nfty/"&gt;Ntfy&lt;/a&gt; buzz on my phone if a machine is in trouble.&lt;/p&gt;
&lt;p&gt;I mentioned a couple of weeks ago that the benefit of doing it in C rather than Node.js was probably not worth the trouble, but then being a fickle developer, decided to write it in Go.&lt;/p&gt;</description></item><item><title>Problems backing up LXC to NFS in Proxmox</title><link>https://devendevour.iankulin.com/problems-backing-up-lxc-to-nfs-in-proxmox/</link><pubDate>Sun, 24 Sep 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/problems-backing-up-lxc-to-nfs-in-proxmox/</guid><description>&lt;p&gt;If you create an unprivileged LXC container on Proxmox, then try to back it up to an NFS share, for example on a NAS, you&amp;rsquo;ll get an error when it tries to build the temporary file.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-08-14-at-9.15.29-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The clue is in the &lt;code&gt;Permission denied&lt;/code&gt; line. It is trying to create a temporary file on my NAS, and failing because of a &lt;a href="https://devendevour.iankulin.com/could-it-be-a-permissions-problem/"&gt;permissions&lt;/a&gt; problem. If I try the same backup to the local storage, it works fine.&lt;/p&gt;</description></item><item><title>Use VS Code to work on remote files</title><link>https://devendevour.iankulin.com/use-vs-code-to-work-on-remote-files/</link><pubDate>Thu, 21 Sep 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/use-vs-code-to-work-on-remote-files/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/dreamshaper_v7_a_cavewoman_sitting_in_a_cave_typing_on_a_small_0.jpg" alt="Cavewoman typing on a MacBook" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve got a script, or some code to work on, and it&amp;rsquo;s on a VM somewhere, you can always &lt;code&gt;ssh&lt;/code&gt; in and use &lt;code&gt;nano&lt;/code&gt; or &lt;a href="https://devendevour.iankulin.com/bloody-vim/"&gt;&lt;code&gt;vim&lt;/code&gt;&lt;/a&gt; to make your edits. Like a caveman. With an archaic editor, no intellisense, and no spell checking.&lt;/p&gt;
&lt;p&gt;Or&amp;hellip;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-08-13-at-3.50.15-pm.png" alt="VS Code connected to a remote server over SSH" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;This magic - of editing a files on a remote server over SSH is achieved by using a Microsoft plugin for VS Code - &amp;ldquo;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh" target="_blank" rel="noopener"&gt;Remote - SSH&lt;/a&gt; &amp;rdquo;&lt;/p&gt;</description></item><item><title>Disable SSH root logins</title><link>https://devendevour.iankulin.com/disable-ssh-root-logins/</link><pubDate>Mon, 18 Sep 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/disable-ssh-root-logins/</guid><description>&lt;p&gt;This always makes me laugh:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-08-03-at-8.01.20-pm.jpg" alt="Screenshot of terminal output full of lines saying &amp;ldquo;Failed password for root&amp;rdquo;" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s like half the traffic on the internet is &lt;a href="https://devendevour.iankulin.com/chinese-hackers-want-to-steal-my-hello-world-container/"&gt;bots&lt;/a&gt; trying random passwords on root accounts over ssh. This is on an Ubuntu VPS on BinaryLane that had only been spun up five minutes or so. Looks like about one attempt every 10 seconds.&lt;/p&gt;
&lt;p&gt;This is why the number three thing on my new install list is to disable root access via ssh. Here&amp;rsquo;s my system - possibly just for Ubuntu and related systems:&lt;/p&gt;</description></item><item><title>Lightweight Web Servers</title><link>https://devendevour.iankulin.com/lightweight-web-servers/</link><pubDate>Fri, 15 Sep 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/lightweight-web-servers/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/dreamshaper_v7_a_web_server_floating_away_because_it_is_so_lig_0.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-08-02-at-9.09.48-pm-2.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using the excellent &lt;a href="https://github.com/louislam/uptime-kuma" target="_blank" rel="noopener"&gt;Uptime Kuma&lt;/a&gt; for my monitoring, but a couple of recent incidents - an external USB mount disappeared on a remote machine, an NVME drive filled up on a different node and stopped backups working because of a configuration error - have made me start to think about more robust monitoring.&lt;/p&gt;
&lt;p&gt;The are many great tools for this - &lt;a href="https://www.nagios.org/" target="_blank" rel="noopener"&gt;Nagios&lt;/a&gt; , &lt;a href="https://prometheus.io/" target="_blank" rel="noopener"&gt;Prometheus&lt;/a&gt; etc. but they are pretty substantial time investments for the excellent power. They can save time series data and display them beautifully. However, all I really want is to add some extra ability to Uptime Kuma.&lt;/p&gt;</description></item><item><title>Cookies, Sessions &amp;amp; Tokens</title><link>https://devendevour.iankulin.com/cookies-sessions-tokens/</link><pubDate>Tue, 12 Sep 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/cookies-sessions-tokens/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/brunofach_flat_screen_illustration_of_a_background_with_cookies_0a1c174a-afdd-43a1-b9f6-ceab96b5aabd.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m up to the point in a web app where it needs to come off my lan and into the hands of a couple of users for alpha feedback. Before that happens, I have to add some sort of login/authentication system since it I want to use real, sensitive data. There&amp;rsquo;s lots of detailed blog posts and videos of how to implement this in an Express app with passport, but what I was missing was the big picture of what actually needs to happen.&lt;/p&gt;</description></item><item><title>Basic VPS disk speed</title><link>https://devendevour.iankulin.com/basic-vps-disk-speed/</link><pubDate>Sat, 09 Sep 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/basic-vps-disk-speed/</guid><description>&lt;p&gt;I couldn&amp;rsquo;t help but measure some VPS disk speeds while I was busting out the &lt;code&gt;fio&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/vps.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Binary Lane only claims &amp;ldquo;pure SSD drives&amp;rdquo; but seems pretty great. The difference between Digital Ocean SSD and NVME is disappointing. Obviously you&amp;rsquo;re sharing a drive with other users, so perhaps this depends on what else is going on.&lt;/p&gt;</description></item><item><title>Sorting out Node package dependencies when cloning old repos</title><link>https://devendevour.iankulin.com/sorting-out-node-package-dependencies-when-cloning-old-repos/</link><pubDate>Wed, 06 Sep 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/sorting-out-node-package-dependencies-when-cloning-old-repos/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/tomins_russian_dolls_inside_out._vector_style_for_storyboard._a57e3973-3942-47ad-9f07-813694c9cecb.jpg" alt="Russian dolls" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;If you clone an old node project and &lt;code&gt;npm install&lt;/code&gt; it, you&amp;rsquo;ll most likely get a bunch of errors and warning messages. If you just decide to yolo it and run the project, you&amp;rsquo;ll get a bunch more.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been doing this exact thing. I want to add some auth to my app, and I&amp;rsquo;ve been following &lt;a href="https://github.com/WebDevSimplified" target="_blank" rel="noopener"&gt;WebDevSimplified&lt;/a&gt; &amp;rsquo;s &lt;a href="https://www.youtube.com/watch?v=-RCnNyD0L-s" target="_blank" rel="noopener"&gt;video&lt;/a&gt; about using &lt;a href="https://www.passportjs.org/packages/passport-npm/" target="_blank" rel="noopener"&gt;passport&lt;/a&gt; . I was building into my app without really understanding what I was doing, ran into problems and decided just to clone his repo and integrate the code into my app. The repo is four years old.&lt;/p&gt;</description></item><item><title>Testing Storage Speed</title><link>https://devendevour.iankulin.com/testing-storage-speed/</link><pubDate>Sun, 03 Sep 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/testing-storage-speed/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/shawnjooste_hero_image_welcome_playful_colorful_tech_company_co_5e8971cb-4cb0-4aa8-938a-610467b485c6.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Now I&amp;rsquo;ve added NVME drives to my nodes, plus added an external NMVE RAID, I&amp;rsquo;ve got quite the collection of storage options. For one of my nodes, it looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-07-23-at-1.20.34-pm.png" alt="Screenshot of Proxmox GUI showing 5 storage options" class="img-responsive"&gt; &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The 256GB NVME the OS is installed to&lt;/li&gt;
&lt;li&gt;The 512GB SSD, currently running ZFS&lt;/li&gt;
&lt;li&gt;The Synology NAS - 4 x 6TB drives in RAID 5 on a 1GB switch&lt;/li&gt;
&lt;li&gt;A pair of 256GB NVME sticks in an external USB3 enclosure set up as a mirrored ZFS pool.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For my dev VM&amp;rsquo;s I often set them up to have their storage on the NAS - it&amp;rsquo;s just super easy to move them around then. The production VM&amp;rsquo;s currently have their storage on the SSD (that machine hasn&amp;rsquo;t had the NVME upgrade yet), but obviously with all these options, it&amp;rsquo;d be interesting to think about what goes where.&lt;/p&gt;</description></item><item><title>Error wiping old drive in Proxmox</title><link>https://devendevour.iankulin.com/error-wiping-old-drive-in-proxmox/</link><pubDate>Thu, 31 Aug 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/error-wiping-old-drive-in-proxmox/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-07-22-at-12.19.42-pm-copy.png" alt="Error: disk/partition &amp;lsquo;/dev/sda3&amp;rsquo; has a holder (500)" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;When I popped in an NVME drive and freshly installed Proxmox to it, I assumed I&amp;rsquo;d just be able to wipe the SDD that had previously been the boot drive to set it up as a ZFS pool. However, when I tried to do the wipe, I was greeted with the error:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;disk/partition &amp;#39;/dev/sda3&amp;#39; has a holder (500)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I assume this means there&amp;rsquo;s a flag set on one of the Proxmox partitions to prevent accidental deletion or Proxmox thought that&amp;rsquo;s where it was running from. It&amp;rsquo;s likely that it&amp;rsquo;s related to this message I had during installation that I haven&amp;rsquo;t seen before:&lt;/p&gt;</description></item><item><title>How to install M.2 SSD in HP G2 800 Mini</title><link>https://devendevour.iankulin.com/how-to-install-m-2-ssd-in-hp-g2-800-mini/</link><pubDate>Mon, 28 Aug 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/how-to-install-m-2-ssd-in-hp-g2-800-mini/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_5821-copy.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;As part of my strategy to not worry about the &lt;a href="https://devendevour.iankulin.com/sdd-wearout-numbers/"&gt;slightly dodgy SMART reporting&lt;/a&gt; on the SDD&amp;rsquo;s in my HP Elitedesk G2 800 Mini Proxmox nodes, I&amp;rsquo;d decided to make use of the full sized &lt;a href="https://en.wikipedia.org/wiki/M.2" target="_blank" rel="noopener"&gt;M.2&lt;/a&gt; slot to install 256GB NVME drives. That way I can boot from those, and have the SSD&amp;rsquo;s running &lt;a href="https://arstechnica.com/information-technology/2020/05/zfs-101-understanding-zfs-storage-and-performance/" target="_blank" rel="noopener"&gt;ZFS&lt;/a&gt; which allows &lt;em&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/8/zpool-scrub.8.html" target="_blank" rel="noopener"&gt;scrubbing&lt;/a&gt; &lt;/em&gt; to check the integrity of all the data. My VM disks can live on this drive.&lt;/p&gt;</description></item><item><title>Hide 'Problems' for a file in VS Code</title><link>https://devendevour.iankulin.com/hide-problems-for-a-file-in-vs-code/</link><pubDate>Fri, 25 Aug 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/hide-problems-for-a-file-in-vs-code/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/apollon_young_man_touching_a_transparent_wall_hiding_code_for_a_524e38cd-fa31-45a3-ab96-dc9b9ed25caa.jpg" alt="Two white bread guys clicking away on a screen wall" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m interested in trying out &lt;a href="https://picocss.com/" target="_blank" rel="noopener"&gt;Pico CSS&lt;/a&gt; - a lightweight CSS library, but when I tossed it into my project, the linter found and reported 29 problems. One of my processes is to just keep that problems tab clear as I work, so I&amp;rsquo;d like that to go away.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-07-20-at-6.54.06-am.jpg" alt="Screenshot of VS Code showing 29 problems detected." class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s possible, but only by &amp;rsquo;excluding&amp;rsquo; the file from your project - it won&amp;rsquo;t show up in the file view either. That&amp;rsquo;s fine with me, I never want to deal with the file so we&amp;rsquo;ll do that, although it might confuse me in seven years if I come back to this project, so I&amp;rsquo;ll drop a link in my .git_ignore as a clue for future me (excluding the file in VS Code doesn&amp;rsquo;t affect git finding it).&lt;/p&gt;</description></item><item><title>Installing a Node app on a server</title><link>https://devendevour.iankulin.com/installing-a-node-app-on-a-server/</link><pubDate>Tue, 22 Aug 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/installing-a-node-app-on-a-server/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/clu_create_an_image_where_a_cute_little_girl_stands_in_a_whimsi_45944303-8475-48ed-9b8d-d291b525138d.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Before I write a fancy Ansible playbook to automatically set up the Nginx/Node combo on my web servers, it might be worth going through how to deploy a Node app so it can run on a server without you being logged in.&lt;/p&gt;
&lt;p&gt;Until now, I&amp;rsquo;ve been running my tests on my laptop, or in a server logged in as myself - sometimes detaching from tmux. But we need a bit more professional set up than that. The process will look something like this:&lt;/p&gt;</description></item><item><title>Digital Ocean first impressions</title><link>https://devendevour.iankulin.com/digital-ocean-first-impressions/</link><pubDate>Sat, 19 Aug 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/digital-ocean-first-impressions/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/peacenode.eth_an_1970s_advert_for_blockchain_deep_sea_ocean_com_78fb2b2f-24c1-4d4f-a703-3f22bada628f_webp.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been thinking about the time it takes me to provision a guest VM in Proxmox. I seem to remember on &lt;a href="https://www.binarylane.com.au/" target="_blank" rel="noopener"&gt;BinaryLane&lt;/a&gt; it was seconds rather than minutes. This seemed to be a good excuse to use the free credit I&amp;rsquo;ve heard about for &lt;a href="https://www.linode.com/lp/free-credit-100/?promo=sitelin100-02162023&amp;amp;promo_value=100&amp;amp;promo_length=60&amp;amp;utm_source=google&amp;amp;utm_medium=cpc&amp;amp;utm_campaign=11178784684_109179223363&amp;amp;utm_term=g_kwd-2629795801_e_linode&amp;amp;utm_content=466889596558&amp;amp;locationid=1000676&amp;amp;device=c_c&amp;amp;gclid=CjwKCAjw-7OlBhB8EiwAnoOEk9lQtzb_l17rAJmoU1KzhTUcWc6TF6C8KBTZU3j6tJ3d1qLWqqiRgxoC6qUQAvD_BwE" target="_blank" rel="noopener"&gt;Linode&lt;/a&gt; or Digital Ocean hundreds of times in podcast adverts, so I claimed the &lt;a href="http://do.co/lnl" target="_blank" rel="noopener"&gt;$200 credit for being a Late Night Linux listener&lt;/a&gt; at Digital Ocean. They extracted $5 out of me in the process, so I guess they are in front on that transaction. $200 would run a little VM for a couple of years at their rates, but of course it&amp;rsquo;s limited to two months, at the end of which I will have an account sitting there, with my credit card already recorded - so all the friction is gone if I need an internet facing machine for some purpose - which is clearly their dastardly plan&lt;/p&gt;</description></item><item><title>Nginx config on Debian/Ubuntu</title><link>https://devendevour.iankulin.com/nginx-config-on-debian-ubuntu/</link><pubDate>Wed, 16 Aug 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/nginx-config-on-debian-ubuntu/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/ea49e0c3-f1d2-4060-83df-18b2cd52e734_damian_he_him_they_linux_tree-directory_structure_in_egyptian_hieroglyphs_-hd.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A quick look at the arrangements around the config settings for nginx. This is based on what I can see in Debian and Ubuntu, but likely it will be most &lt;code&gt;apt&lt;/code&gt; flavoured distros. Others may well be different, I know CentOS is.&lt;/p&gt;
&lt;h3 id="context"&gt;Context&lt;/h3&gt; &lt;p&gt;If the way the configs for nginx are arranged seems a little complicated, it&amp;rsquo;s helpful to keep in mind there&amp;rsquo;s a couple of challenges that are being addressed with that complexity.&lt;/p&gt;</description></item><item><title>Ansible with Secrets</title><link>https://devendevour.iankulin.com/ansible-with-secrets/</link><pubDate>Sun, 13 Aug 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ansible-with-secrets/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/danbearpig_construction_process_photos_of_an_enormous_hyper-sec_4bbf6350-647d-4e32-971b-cd2041cb52a9_webp.jpg" alt="Two men standing in front of a giant vault door" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;We wrote a nice &lt;a href="https://devendevour.iankulin.com/first-ansible-playbook/"&gt;little Ansible playbook&lt;/a&gt; the other day to install nginx on our web servers and ensure it was running. We were able to store the usernames in the &lt;code&gt;hosts&lt;/code&gt; inventory file using the a&lt;code&gt;nsible_ssh_user&lt;/code&gt; variable. Then, we ran the playbook with the command:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ansible-playbook web_installs.yaml --ask-become-pass&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This asked us the password to use with the usernames in the &lt;code&gt;hosts&lt;/code&gt; file. Luckily that day, it was the same username/password combo to use for sudo on every server. What happens if that&amp;rsquo;s not the case? Here&amp;rsquo;s our new hosts file for today. There&amp;rsquo;s a cool new sysadmin in town - Jane.&lt;/p&gt;</description></item><item><title>Bloody VIM</title><link>https://devendevour.iankulin.com/bloody-vim/</link><pubDate>Thu, 10 Aug 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/bloody-vim/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/mikemol_female_oracle_database_administrator_seething_over_a_de_41a485b2-af77-47db-9db0-73dfa14e4ad0.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Vim is a highly configurable text editor built to make creating and changing any kind of text very efficient. It is included as &amp;ldquo;vi&amp;rdquo; with most UNIX systems and with Apple OS X.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.vim.org/" target="_blank" rel="noopener"&gt;vim.org&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You will encounter vi/vim as the incomprehensible text editor that pops up by default when you need to edit something in your sysadmin journey. Perhaps you issued the command to edit your Ansible vault, perhaps you forgot to add a message to a commit. It&amp;rsquo;s going to be unavoidable.&lt;/p&gt;</description></item><item><title>Finding the host IP from inside a Docker container</title><link>https://devendevour.iankulin.com/finding-the-host-ip-from-inside-a-docker-container/</link><pubDate>Mon, 07 Aug 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/finding-the-host-ip-from-inside-a-docker-container/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/ak_writer_the_lost_whale_story_e5979736-74f1-4404-9dd1-8c6c1047c244.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Having successfully set up and tested my node.js api handling app behind nginx on a development VM in the homelab, I decided to move it to my VPS so I could start using it for real. I had a bit of trouble finding the nginx.conf files on the VPS, until I remembered I was running nginx in a docker container on this machine!&lt;/p&gt;
&lt;p&gt;I got everything set up, I could hit the domain in a web browser and get served the static page, and I could &amp;lt;domain_name&amp;gt;:3000/api/gnp_temp.txt and get the file delivered by the node script, but if I tried &amp;lt;domain_name&amp;gt;/api/gnp_temp.txt - &amp;ldquo;Bad Gateway&amp;rdquo;.&lt;/p&gt;</description></item><item><title>nginx in Front of a node.js app</title><link>https://devendevour.iankulin.com/nginx-in-front-of-a-node-js-app/</link><pubDate>Fri, 04 Aug 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/nginx-in-front-of-a-node-js-app/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/jonaslittorin_strictly_digital_content_web_server_technology_we_fad86a29-71f0-439c-9900-2134fea30897.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;NGINX is a great webserver and reverse proxy - as in it can hand off requests to other web-servers. That&amp;rsquo;s the situation I want to have set up on my VPS. I want NGINX to handle incoming requests - some of them will just be sorted out by returning static HTML, others (like the weather api I&amp;rsquo;ve been playing with) need to be handed off to other services to respond to.&lt;/p&gt;</description></item><item><title>Where to go after Reddit</title><link>https://devendevour.iankulin.com/where-to-go-after-reddit/</link><pubDate>Tue, 01 Aug 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/where-to-go-after-reddit/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/rpg_40_diaspora_of_reddit_users_searching_for_new_homes_in_a_d_0.jpg" alt="Diaspora of Reddit users searching for new homes in a dystopian landscape of abandoned technology" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A big chunk of my mindless doomscrolling used to go to Reddit, but also, Reddit posts from the various communities were frequently the useful results when googling error messages. I lurked in many a sub-reddit, but only posted in a couple - usually r/self-hosted or r/Homelab.&lt;/p&gt;
&lt;p&gt;The problematic treatment of the communities in the leadup to their IPO has been well publicised, and the short blackout by some subreddits seemed to have zero effect on the company&amp;rsquo;s approach to it&amp;rsquo;s users (which is in fact what they have to sell). Those subreddits, and many others are still working, but (and perhaps I&amp;rsquo;m imagining this) seem somehow thinner. Additionally, I feel like it&amp;rsquo;s a fragile arrangement - the company has shown how they will deal with their communities, so depending on them in the long term does not seem wise, or even, somehow, ethical - like I&amp;rsquo;m crossing a picket line.&lt;/p&gt;</description></item><item><title>ZFS Basics on Proxmox</title><link>https://devendevour.iankulin.com/zfs-basics-on-proxmox/</link><pubDate>Sat, 29 Jul 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/zfs-basics-on-proxmox/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/bitlord_imagine_a_futuristic_ai-inspired_structure_in_the_backg_b80936d4-6746-423f-a620-f8167c2fc802.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a keen listener of the &lt;a href="https://2.5admins.com/" target="_blank" rel="noopener"&gt;2.5 Admins&lt;/a&gt; podcast in which there&amp;rsquo;s frequent enumeration of the advantages of &lt;a href="https://itsfoss.com/what-is-zfs/" target="_blank" rel="noopener"&gt;ZFS&lt;/a&gt; as a file system. So much so, that I&amp;rsquo;ve had occasional twinges or regret about the money I spent on the Synology - although it has been boringly reliable and does everything I need.&lt;/p&gt;
&lt;p&gt;Proxmox has some built in support for ZFS, including through the web GUI. So I&amp;rsquo;ve been itching to give it a try.&lt;/p&gt;</description></item><item><title>First Ansible Playbook</title><link>https://devendevour.iankulin.com/first-ansible-playbook/</link><pubDate>Wed, 26 Jul 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/first-ansible-playbook/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/biomage_biomechanical_cyborg_computer_hacker_keyboard_protrudin_3d895c1b-0776-4f6e-b1a6-733b5622ea5d.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;In the &lt;a href="https://devendevour.iankulin.com/getting-started-with-ansible/"&gt;previous post&lt;/a&gt; , we looked at getting up and running with Ansible, including using the ad-hoc mode to send commands to our servers. We had a inventory file called hosts that had groups of server IP addresses and a simple &lt;code&gt;ansible.cfg&lt;/code&gt; file that pointed to our inventory file.&lt;/p&gt;
&lt;h3 id="playbooks"&gt;Playbooks&lt;/h3&gt; &lt;p&gt;Ansible playbooks are used to collect together a description of the state we want in a server. When the playbook is executed, Ansible figures out what things need need changed, and changes them. If you&amp;rsquo;re used to the procedural nature of a bash script, where things proceed from one step to the next, and there might be decision branches, this requires an adjustment in your thinking. This is similar to the adjustment I had getting my head around &lt;a href="https://betterprogramming.pub/swiftui-understanding-declarative-programming-aaf05b2383bd" target="_blank" rel="noopener"&gt;SwiftUI&lt;/a&gt; , and moving from JS to &lt;a href="https://levelup.gitconnected.com/why-react-is-declarative-a300d1e930b7?gi=3d11485226b4" target="_blank" rel="noopener"&gt;React&lt;/a&gt; .&lt;/p&gt;</description></item><item><title>Proxmox 8.0 Install</title><link>https://devendevour.iankulin.com/proxmox-8-0-install/</link><pubDate>Sun, 23 Jul 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/proxmox-8-0-install/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/alaviles_experience_the_gold_standard_in_local_desktop_virtuali_f1a1d3a4-d7b1-489f-be57-41388033eea1.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m normally a x.1 release type of sysadmin, but the increasing temptation of installing Proxmox 8.0 while I&amp;rsquo;ve got some time off, and the fact that I&amp;rsquo;ve got a cluster, so I can just move the VM&amp;rsquo;s around all adds up to thinking I&amp;rsquo;ll do that today.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/cluster-2.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how my system works. It consists of three HP-800 mini G2&amp;rsquo;s. &lt;code&gt;pve-prod1&lt;/code&gt; is a bit fancier - i7 6700T and 32GB, the other two are i5 6500T and 16GB. The production VM&amp;rsquo;s use the local SSD but backups go to the NAS. All the machines are currently running Proxmox 7.4. They are not clustered in the proper sense - I don&amp;rsquo;t need high availability, and I don&amp;rsquo;t want to run them all the time. &lt;code&gt;pve-prod1&lt;/code&gt; runs 24/7 and I just power up &lt;code&gt;pve-dev1&lt;/code&gt; when I&amp;rsquo;m working on something.&lt;/p&gt;</description></item><item><title>Getting Started with Ansible</title><link>https://devendevour.iankulin.com/getting-started-with-ansible/</link><pubDate>Wed, 19 Jul 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/getting-started-with-ansible/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/cyberpunk_24_k_hyper_realistic_a_thousand_details_hyper_detaile_841f4769-e869-497f-a804-c9fade21e150.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Ansible is a system for executing commands on remote systems. It allows a declarative approach - so if you run a playbook (the system configuration files are called playbooks) that says a system has a Docker container running Jellyfin, Ansible will check if that&amp;rsquo;s true, and if not, make it so. Ansible is best used when you have a large number of systems to maintain, but even with a small number, it serves to document systems as well as to automate their creation.&lt;/p&gt;</description></item><item><title>How to recover a docker run command</title><link>https://devendevour.iankulin.com/how-to-recover-a-docker-run-command/</link><pubDate>Sun, 16 Jul 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/how-to-recover-a-docker-run-command/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/andywatt83_a_developer_environment_in_a_container_using_docker_051f6abb-8c38-4b2d-85cf-7c3f8744118b.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Imagine if, lets say hypothetically, you&amp;rsquo;d set up an application months ago with a &lt;code&gt;docker run&lt;/code&gt; command. Then you&amp;rsquo;d heard there had been an update to the app because of a security update. So you need to stop/remove the container, pull a new image and restart it, trouble is, you don&amp;rsquo;t remember the exact &lt;code&gt;run&lt;/code&gt; command you used to start it.&lt;/p&gt;
&lt;p&gt;This didn&amp;rsquo;t happen to me, since all my vm setups are in git as markdown (I&amp;rsquo;m pre-Ansible), but I did google how to do this thinking that there would be an easy way before I bothered to look through my config files.&lt;/p&gt;</description></item><item><title>Updating SSL Certificates</title><link>https://devendevour.iankulin.com/updating-ssl-certificates/</link><pubDate>Wed, 12 Jul 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/updating-ssl-certificates/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/0_logo_minimal_modern_vector_it_tools_security_anonymous_vuln_31d19059-50fd-4809-bff1-a13ef295807e.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;When I first installed my SSL certificates, &lt;a href="https://devendevour.iankulin.com/installing-ssl-certificates-with-nginx-on-docker/"&gt;I mentioned&lt;/a&gt; it&amp;rsquo;s a process I need to automate before they came up for expiry, but here we are ten days out, and I haven&amp;rsquo;t done that yet, but I have been keeping an eye on it though the excellent display and notifications set up in &lt;a href="https://devendevour.iankulin.com/uptime-kuma-nfty/"&gt;Uptime Kuma&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-07-10-at-5.36.01-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Updating the certificates is easy. When I went into the site at PorkBun (where I purchased the domain and who do the primary DNS for the site, the next certificates were sitting there to be downloaded. My existing certificates were due to expire on 30th July, and these had been generated on 3rd July.&lt;/p&gt;</description></item><item><title>Unlearning Relational DB</title><link>https://devendevour.iankulin.com/unlearning-relational-db/</link><pubDate>Sun, 09 Jul 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/unlearning-relational-db/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pawann_seamless_computers_data_storage_databases_processors_on_28c2c788-6e8e-4dfd-a58f-8a468dd647e0.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Some of my first university programming was writing &lt;a href="https://en.wikipedia.org/wiki/CICS" target="_blank" rel="noopener"&gt;CICS&lt;/a&gt; COBOL transactions against IBM&amp;rsquo;s &lt;a href="https://en.wikipedia.org/wiki/IBM_Db2" target="_blank" rel="noopener"&gt;DB2&lt;/a&gt; relational database. My commercial work after uni was mostly written in Clipper which was a sort of compiled version of &lt;a href="https://en.wikipedia.org/wiki/DBase" target="_blank" rel="noopener"&gt;dBase&lt;/a&gt; and used the same data file format. The minimal web work I did in the before times relied on &lt;a href="https://en.wikipedia.org/wiki/MySQL" target="_blank" rel="noopener"&gt;MySQL&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;All of which is to say, I&amp;rsquo;m very comfortable designing relational database schema, and I understand what&amp;rsquo;s going on at the disk level when they are being accessed and written to.&lt;/p&gt;</description></item><item><title>How to deploy a Node.js app</title><link>https://devendevour.iankulin.com/how-to-deploy-a-node-js-app/</link><pubDate>Wed, 05 Jul 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/how-to-deploy-a-node-js-app/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/naresh_create_a_github_account_and_a_new_repository._install_gi_c8bce4b2-201f-422b-815c-bb6286fb000a.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;This is one of those things that is simple once you know it. I had my &lt;a href="https://devendevour.iankulin.com/using-node-js-to-return-a-static-file/"&gt;tiny Node service working&lt;/a&gt; on my MacBook, but how do I run it on the server?&lt;/p&gt;
&lt;h3 id="native-or-container"&gt;Native or Container&lt;/h3&gt; &lt;p&gt;Obviously I need Node.js installed on the server, should I have it in a Docker container, or native on the machine. There&amp;rsquo;s no clear answer here - in a container set up with Docker Compose might be more in line with my ideology of treating machines as disposable, but a native install is simpler, and I probably want to make life simpler at this stage when I&amp;rsquo;m learning everything.&lt;/p&gt;</description></item><item><title>Using Node.js to return a static file</title><link>https://devendevour.iankulin.com/using-node-js-to-return-a-static-file/</link><pubDate>Sun, 02 Jul 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/using-node-js-to-return-a-static-file/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/rajeshtva_node.js_609056a9-3b73-46f5-bc4f-c1f110e3a367.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;As mentioned in the &lt;a href="https://devendevour.iankulin.com/complicating-the-temperature-api/"&gt;previous post&lt;/a&gt; , stage one is just to return the same static text file, but from the Node server, rather than NGINX. That&amp;rsquo;s non-trivial to a rank beginner since I need to figure out 1) how to serve a static file from Node, and 2) how to configure NGINX to hand off calls to the API to Node. This post will look at both of those, but it&amp;rsquo;s first probably worth just setting out what each of the puzzle pieces are.&lt;/p&gt;</description></item><item><title>Complicating the Temperature API</title><link>https://devendevour.iankulin.com/complicating-the-temperature-api/</link><pubDate>Wed, 28 Jun 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/complicating-the-temperature-api/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/gandalfthebeard_personal_dashboard_with_share_prices_photograph_bda71695-3d15-4521-9df1-8170f5906d8b.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been slammed with other work, so my web dev learning has fallen well behind. Luckily, the YouTube procrastination algorithm noticed this and suggested I watch a video from &lt;a href="https://www.youtube.com/@codewithcon" target="_blank" rel="noopener"&gt;CodeWithCon&lt;/a&gt; titled &lt;a href="https://www.youtube.com/watch?v=KNa-wMpry00&amp;amp;list=PLkJHe6eU_tzeoe7vKUEa4MrS74CpVEwdI&amp;amp;index=3&amp;amp;t=305s" target="_blank" rel="noopener"&gt;Learn Backend in 10 MINUTES&lt;/a&gt; .&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/KNa-wMpry00?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;Since I was watching a video of a guy learning to land a C152 at St Baths (a skill I do &lt;em&gt;not&lt;/em&gt; need) at the time, it was hard to argue with myself that I didn&amp;rsquo;t have ten minutes to learn all of backend programming.&lt;/p&gt;</description></item><item><title>HDD Swap on A1278 MacBook Pro</title><link>https://devendevour.iankulin.com/hdd-swap-on-a1278-macbook-pro/</link><pubDate>Wed, 10 May 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/hdd-swap-on-a1278-macbook-pro/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/dexxx3d_computer_repair_shop__a_lot_of_computer_laptop_for_repa_7ee46d83-216e-4f67-81af-ee3c7037e6e7.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;My MacBook died, I guess about three years ago. It was randomly difficult for a week or so, but then just behaving as if it had no hard drive at all. It&amp;rsquo;s been in a drawer ever since waiting for me to replace the hard drive and see if I could sell it, which I never quite got to.&lt;/p&gt;
&lt;p&gt;I mentioned a while ago that I&amp;rsquo;d &lt;a href="https://devendevour.iankulin.com/linux-on-hp-mini-110/"&gt;borrowed an old Atom powered HP Mini 110&lt;/a&gt; to play with a Linux desktop machine, partly for fun &amp;amp; learning, and partly for a first-class SPICE experience (also fun). Meanwhile I&amp;rsquo;ve got an old but still sexy Intel MacBook Pro sitting in a drawer - that doesn&amp;rsquo;t make sense!&lt;/p&gt;</description></item><item><title>Containers</title><link>https://devendevour.iankulin.com/containers/</link><pubDate>Sun, 07 May 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/containers/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/troop_team_of_4_programmers_productively_working_standing_with__cb8656e7-ffd0-41df-b5bb-778ff18fd910.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a few things that really strike me as significant improvements to life since I was commercially developing 20 years ago:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accessing information - the first time I &lt;em&gt;bought&lt;/em&gt; the development stack to write commercial software against the Windows SDK it came in a huge carton with, I guess, fifteen or so 2&amp;quot; thick books. That was how you looked things up in those days. Fast forward to an internet connected world of websites, stack exchange, Discord and ChatGPT. So much better.&lt;/li&gt;
&lt;li&gt;Open Source - is an actual useful thing that the entire connected world runs on - not just a weird hippy idea. It&amp;rsquo;s almost routine to open source your code now and everyone benefits from that.&lt;/li&gt;
&lt;li&gt;Containers - &amp;ldquo;getting things working&amp;rdquo; used to be a thing. Most times now I want to spin something up to play with it, it just works because all the dependencies are bundled with it, and it doesn&amp;rsquo;t mutate the environment in any way I don&amp;rsquo;t know about. There&amp;rsquo;s no friction to run a giant app, and no hangover for the OS when I nuke it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I love this great explanation from Coderized about containers - I wish I&amp;rsquo;d seen it five months ago.&lt;/p&gt;</description></item><item><title>Beginning Python</title><link>https://devendevour.iankulin.com/beginning-python/</link><pubDate>Fri, 05 May 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/beginning-python/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/mhbaando_girl_coding_in_a_computer_with_python_language_4702d51a-c38b-45b8-b12d-9daa4bc2110d.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve gotten interested in &lt;a href="https://plaintextaccounting.org/" target="_blank" rel="noopener"&gt;Plain Text Accounting&lt;/a&gt; , and I&amp;rsquo;m looking at using &lt;a href="https://beancount.github.io/" target="_blank" rel="noopener"&gt;BeanCount&lt;/a&gt; for small business &amp;amp; personal finances. If you haven&amp;rsquo;t heard of this, it&amp;rsquo;s a command line program that uses text files with a human comprehensible syntax to define transactions, then acts on them to create the needed reports etc. A side benefit for developers is that it&amp;rsquo;s then easily version controlled using GIT, and of course there&amp;rsquo;s VS Code plugins for it.&lt;/p&gt;</description></item><item><title>Outside Temperature From an API in a Shell Script</title><link>https://devendevour.iankulin.com/outside-temperature-from-an-api-in-a-shell-script/</link><pubDate>Wed, 03 May 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/outside-temperature-from-an-api-in-a-shell-script/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/challengereality_a_highly_detailed_ultra_high_resolution_hologr_1509798a-548d-4528-bcc7-cb1f2bb30a0e.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m interested in &lt;a href="https://devendevour.iankulin.com/linux-shell-script-for-temperature-logging/"&gt;collecting some internal temperature data&lt;/a&gt; from my servers to look at the effect of adding an NMVe drive. Last week we had a couple of warm days immediately followed by a couple of cool ones. I imagine a 20° ambient temperature change could effect the server temperatures, so I thought it would be good to add that to my temperature logs.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have a weather station or other automated system for collecting the temperature, but there are several commercial sources for this data which, while probably not as good as a sensor in the server room, will be fine for our purposes.&lt;/p&gt;</description></item><item><title>Running a Browser Remotely - n.eko</title><link>https://devendevour.iankulin.com/running-a-browser-remotely-n-eko/</link><pubDate>Tue, 02 May 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/running-a-browser-remotely-n-eko/</guid><description>&lt;p&gt;When I installed the backup NAS and a media server at the remote site, one of the jobs on my list was to reserve the IP addresses for the NAS, node, and the VM in the local router. I carefully did that, but when I got home (200 km later) and opened my laptop, the browser page was open on the DHCP settings with a table of mac addresses I&amp;rsquo;d added, and the reserved IP&amp;rsquo;s, and at the bottom of the page, a large blue &amp;ldquo;Apply Changes&amp;rdquo; button. Had I pressed that button to save my changes correctly? I&amp;rsquo;m not sure.&lt;/p&gt;</description></item><item><title>ISO wrangling - Etcher and Ventoy</title><link>https://devendevour.iankulin.com/iso-wrangling-etcher-and-ventoy/</link><pubDate>Mon, 01 May 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/iso-wrangling-etcher-and-ventoy/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/santanica_photo_of_personalized_usb_drive_with_cmyk_paint_blots_1b77f141-0e68-46aa-af6b-cb9d1d07a9b6.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;If you fiddle around with computers, and especially with Linux drives, you&amp;rsquo;ll often find yourself with an ISO file you need to boot a device from. These can&amp;rsquo;t just be copied onto an existing USB or SD card - they need to be bootable, so you&amp;rsquo;ll need a special program to write the ISO to the storage device.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-04-23-at-2.02.44-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Previously I&amp;rsquo;ve been a big fan of &lt;a href="https://www.balena.io/etcher" target="_blank" rel="noopener"&gt;Balena Etcher&lt;/a&gt; . It couldn&amp;rsquo;t be much more simple - you chose the ISO file you&amp;rsquo;ve downloaded from somewhere, chose your removable drive (it intelligently hides the non-removable drives to prevent you from accidentally wiping your hard disk), then tell it to do it&amp;rsquo;s thing.&lt;/p&gt;</description></item><item><title>Git/GutHub - macOS - marking file as executable</title><link>https://devendevour.iankulin.com/git-guthub-macos-marking-file-as-executable/</link><pubDate>Sun, 30 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/git-guthub-macos-marking-file-as-executable/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/uwillc_a_computer_screen_displaying_the_github_page_3622791d-5c28-458b-acac-8f2ca2066179.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m working on the world&amp;rsquo;s shortest shell script - it&amp;rsquo;s called by &lt;code&gt;cron&lt;/code&gt; to pull down a JSON weather report to a text file using &lt;code&gt;curl&lt;/code&gt; so I can expose it on an Nginx endpoint. The purpose is to allow me to hammer that weather API from multiple machines I control without violating the TOS of my free API key.&lt;/p&gt;
&lt;p&gt;Because I&amp;rsquo;m learning all the things, instead of just creating this on the VPS where it runs, it&amp;rsquo;s cloned from my GitHub repo for that machine. I&amp;rsquo;m creating and editing the file in VS Code on macOS, pushing to Github, then pulling the changes on the Ubuntu VPS. The intention is that this will eventually become automated with a Github action.&lt;/p&gt;</description></item><item><title>Installing SSL Certificates with Nginx on Docker</title><link>https://devendevour.iankulin.com/installing-ssl-certificates-with-nginx-on-docker/</link><pubDate>Sat, 29 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/installing-ssl-certificates-with-nginx-on-docker/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/starliu_building_trust_with_ai_challenges_and_solutions_a519169f-8b94-4b34-88d9-e2e635bc5996.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;When you&amp;rsquo;ve successfully got Nginx running in a Docker container, AND got your &lt;a href="https://devendevour.iankulin.com/adding-a-domain-name-to-a-vps/"&gt;domain correctly pointing&lt;/a&gt; at your nascent website, you&amp;rsquo;re then going to want to set it up for encrypted, and therefore trusted, browsing with SSL.&lt;/p&gt;
&lt;h3 id="certificates"&gt;Certificates&lt;/h3&gt; &lt;p&gt;A couple of posts ago, I &lt;a href="https://devendevour.iankulin.com/adding-a-domain-name-to-a-vps/"&gt;mentioned&lt;/a&gt; that it was simpler to let Porkbun be the authoritative nameserver for a domain. Part of the reason for that is that if we do that, Porkbun had a button you can press which connects to LetsEncrypt and generates the certificates for you. This usually takes an hour or so, then you&amp;rsquo;ll be able to download the bundle from that same page.&lt;/p&gt;</description></item><item><title>Adding a Domain Name to a VPS</title><link>https://devendevour.iankulin.com/adding-a-domain-name-to-a-vps/</link><pubDate>Fri, 28 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/adding-a-domain-name-to-a-vps/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/sjramblings_io_aws_route_53_resolver_is_a_dns_resolution_servic_227bbb4f-1ff3-455d-84fa-5e8ea4310df8_png_92.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve had a small &lt;a href="https://www.binarylane.com.au/" target="_blank" rel="noopener"&gt;BinaryLane VPS&lt;/a&gt; for a while that I use for homelab type stuff, but now need to serve a tiny amount of JSON from it. A longer term plan is to use it as a &lt;a href="https://www.wireguard.com/" target="_blank" rel="noopener"&gt;Wireguard&lt;/a&gt; tunnel back to my cluster at home to expose the services that need to be internet facing. I&amp;rsquo;ve also had a domain name I bought from &lt;a href="https://porkbun.com/products/domains" target="_blank" rel="noopener"&gt;Porkbun&lt;/a&gt; sitting round for a bit, so it&amp;rsquo;s probably a good time to join them up.&lt;/p&gt;</description></item><item><title>Linux Shell Script for Temperature Logging</title><link>https://devendevour.iankulin.com/linux-shell-script-for-temperature-logging/</link><pubDate>Thu, 27 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/linux-shell-script-for-temperature-logging/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/jimmy_e_a_computer_melting_on_an_office_desk_in_the_style_of_da_337547b0-ed21-46d5-8857-15d6dc8f6134.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A potential solution to my concern about the either perfect, or nearly dead, SSD would be to add a NVMe disk to the M.2 slot in the HP Elitedesk 800 G2&amp;rsquo;s. I&amp;rsquo;d use those to boot from and run Proxmox, then the existing SSD&amp;rsquo;s on each node in the cluster would just be part of the CephFS pool that has some redundancy built into it and hosts the VMs that are not using the NAS for their storage.&lt;/p&gt;</description></item><item><title>SDD Wearout numbers</title><link>https://devendevour.iankulin.com/sdd-wearout-numbers/</link><pubDate>Tue, 25 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/sdd-wearout-numbers/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/lionovich_computer_cries_because_of_dead_ssd_6149b1c0-005e-41d2-a912-eb864a307555.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t understand why the default Proxmox install sets up the storage the way it does - with the available disk split up into an LVM and an LVM thin storage - so I&amp;rsquo;ve been reading this excellent &lt;a href="https://blog.programster.org/proxmox-storage-guide" target="_blank" rel="noopener"&gt;Proxmox Storage Guide&lt;/a&gt; by Programster (spoiler - the LVM thin makes VM snapshots easier).&lt;/p&gt;
&lt;p&gt;At one point in the post they mention that you can see the &amp;ldquo;Wearout&amp;rdquo; percentage for any SSD drives in the Proxmox GUI, so of course, since I now own five second hand HP Elitedesk 800 G1/G2&amp;rsquo;s all with SSD drives, I dived in to have a look at each drive and found this.&lt;/p&gt;</description></item><item><title>Why use './' in front of filenames?</title><link>https://devendevour.iankulin.com/why-use-in-front-of-filenames/</link><pubDate>Sun, 23 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/why-use-in-front-of-filenames/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pino_path_in_the_middle_of_tall_wheat_stalks_sunset_cartoon_sty_dc2ca25c-dec8-4e9b-b204-6236bc3e8b08.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;In Linux (and MS-DOS I guess) the period signifies the current directory, so if I have a file in the current directory called &lt;code&gt;test.txt&lt;/code&gt;, I can refer to it as &lt;code&gt;test.txt&lt;/code&gt; or &lt;code&gt;./test.txt&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ian@enrico-rider:~$ cat test.txt
test
ian@enrico-rider:~$ cat ./test.txt
test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I mostly see this in references to files in HTML and have often wondered why. Here it is being used in a Udemy course I&amp;rsquo;m following.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-04-19-at-10.49.00-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s one of those things that&amp;rsquo;s difficult to Google, so these days my reflex is to ask ChatGPT such questions.&lt;/p&gt;</description></item><item><title>Mounting NFS shares into LXC containers</title><link>https://devendevour.iankulin.com/mounting-nfs-shares-into-lxc-containers/</link><pubDate>Fri, 21 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/mounting-nfs-shares-into-lxc-containers/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/adalion_metcalfes_law_a_computer_connected_to_two_computers_con_9b3dd611-2c9e-4948-a3df-2dd121f0492b.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m playing with &lt;a href="https://syncthing.net/" target="_blank" rel="noopener"&gt;Syncthing&lt;/a&gt; with the idea that it might be a good replacement for Dropbox. There wasn&amp;rsquo;t a Docker container listed in the install options, so I thought this might be a good app to run in an LXC.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to use a share from the NAS, and I&amp;rsquo;m assuming I&amp;rsquo;ll need it mount it into the container for Syncthing to access. I&amp;rsquo;m experienced enough to know that I&amp;rsquo;m going to want a privileged container, and I thought I&amp;rsquo;d done all the NFS sharing correctly, but when I tried to mount the NFS share, I was getting an error.&lt;/p&gt;</description></item><item><title>Running Multiple Linux Commands in One Line</title><link>https://devendevour.iankulin.com/running-multiple-linux-commands-in-one-line/</link><pubDate>Wed, 19 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/running-multiple-linux-commands-in-one-line/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/luc_legay_hyperrealistic_wide_angle_shot_of_a_futuristic_milita_c8fa0a81-4a16-4314-a490-c89221c4060f-1.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;m constantly standing up Linux virtual machines and containers - almost always of the &lt;code&gt;apt&lt;/code&gt; variety, I&amp;rsquo;m constantly typing in:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apt update
apt upgrade
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then hitting enter again to allow whatever installation is needed to proceed. I&amp;rsquo;ve noticed in some of the commands I&amp;rsquo;ve been pasting in from installation instructions or StackExchange solutions have been separated by characters that look like it allows several commands to be run one after the other. To cut a long story short, the commands above could be entered like this with double ampersands:&lt;/p&gt;</description></item><item><title>Linux on HP Mini 110</title><link>https://devendevour.iankulin.com/linux-on-hp-mini-110/</link><pubDate>Mon, 17 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/linux-on-hp-mini-110/</guid><description>&lt;p&gt;I&amp;rsquo;ve been furthering my Linux education by playing with some desktop distros in VMs, but it&amp;rsquo;s not a great experience accessing them through the Proxmox web GUI. The alternative to this is to use a good &lt;a href="https://en.wikipedia.org/wiki/Simple_Protocol_for_Independent_Computing_Environments" target="_blank" rel="noopener"&gt;SPICE&lt;/a&gt; client on the remote desktop, but there is &lt;a href="https://forum.proxmox.com/threads/access-vm-thru-spice-on-osx.66727/" target="_blank" rel="noopener"&gt;not a simple good solution&lt;/a&gt; for this for MacOS.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been playing with the idea of picking up an old i3/i5 Thinkpad - these are around the AUD130 mark on eBay, to run a Linux distro with the main idea being to use it to SPICE into my VMs.&lt;/p&gt;</description></item><item><title>Recursively Deleting Files in Linux</title><link>https://devendevour.iankulin.com/recursively-deleting-files-in-linux/</link><pubDate>Fri, 14 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/recursively-deleting-files-in-linux/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/nitchos_movie_scene_still_gravity_mixed_with_melancholia_univer_e6f94fb5-1e41-4b98-a749-3d6693a5ee6c.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using this rsync command to backup files from my NAS to a USB drive. The &amp;ndash;excludes are to avoid copying over some junk hidden files - some created by MacOS and some by Synology.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo rsync -rvit --exclude &amp;#39;*@eaDir*&amp;#39; --exclude &amp;#39;.DS_Store&amp;#39; /volume1/media/ /volumeUSB1/usbshare1-2/media --del
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;.DS_Store&lt;/code&gt; files seem to be dropped by MacOS every time I view a directory on the NAS from my MacBook. They&amp;rsquo;re not doing any harm, and they presumably do something handy for the Mac - remembering the view settings for that folder or some such. Nevertheless, they annoy me. It makes sense to not back them up - they don&amp;rsquo;t serve any useful purpose in that context.&lt;/p&gt;</description></item><item><title>Proxmox LXC backup to NFS share failing</title><link>https://devendevour.iankulin.com/proxmox-lxc-backup-to-nfs-share-failing/</link><pubDate>Wed, 12 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/proxmox-lxc-backup-to-nfs-share-failing/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/goodatsportz_filing_cabinet_on_fire_overflowing_with_more_flami_d6bd199d-5932-46a7-969b-0165748f83fb.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I was doing updates on all my nodes and VM&amp;rsquo;s today, and backing up the VMs that aren&amp;rsquo;t already on a backup schedule. On my dev machine I have a Debian LXC container that I mostly just use for trying out Linux commands and playing around. I used to have a backup of it that I used a lot - after playing around I like to set it back to a fresh install plus my ssh keys - but I lost it somehow when moving the VM to new metal.&lt;/p&gt;</description></item><item><title>Using NAS for Proxmox backups</title><link>https://devendevour.iankulin.com/using-nas-for-proxmox-backups/</link><pubDate>Mon, 10 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/using-nas-for-proxmox-backups/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pisskatt_wrapped_eth_cryptocurrency_coins_wrapped_8k_2fe1bfde-8bed-4851-ac42-6dc00e4ef98f.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="https://devendevour.iankulin.com/moving-a-vm-between-two-proxmox-hosts/"&gt;A few weeks ago&lt;/a&gt; , I was very excited to be able to take a snapshot of a virtual machine, copy it across the network from that Proxmox node, copy it back across the network to a different Proxmox node, start it there, and have it up and running, without it noticing it was actually on different hardware.&lt;/p&gt;
&lt;p&gt;Backing up a VM is pretty simple, you just click on the node, choose &lt;em&gt;Backup&lt;/em&gt; and click the &lt;em&gt;Backup Now&lt;/em&gt; button. The ease, and completeness of backing up a VM is one of the main reasons I&amp;rsquo;m using Proxmox for my systems.&lt;/p&gt;</description></item><item><title>Where Do Docker Container Logs Go?</title><link>https://devendevour.iankulin.com/where-do-docker-container-logs-go/</link><pubDate>Sat, 08 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/where-do-docker-container-logs-go/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/containersfallingoffaship_87726525.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m still loving the Docker &amp;ldquo;just works&amp;rdquo; magic, despite their &lt;a href="https://www.theregister.com/2023/03/17/docker_free_teams_plan/" target="_blank" rel="noopener"&gt;terrible PR skills&lt;/a&gt; , but sometimes I start a container, then the &lt;code&gt;docker ps -a&lt;/code&gt; shows it exited almost immediately. Clearly I&amp;rsquo;ve made a mistake, but there&amp;rsquo;s no stdout error message to tell me what I&amp;rsquo;ve done wrong, where is it.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at an example from today. I&amp;rsquo;m testing &lt;a href="https://filebrowser.org/" target="_blank" rel="noopener"&gt;Filebrowser&lt;/a&gt; on a dev machine before I deploy it to the remote backup machine I&amp;rsquo;m assembling. And instead of following the &lt;a href="https://filebrowser.org/installation" target="_blank" rel="noopener"&gt;official instructions&lt;/a&gt; , I&amp;rsquo;m following a &lt;a href="https://bobcares.com/blog/filebrowser-installation-in-docker/" target="_blank" rel="noopener"&gt;blog post&lt;/a&gt; which has a few more details, but unfortunately also a small error.&lt;/p&gt;</description></item><item><title>Allowing Proxmox to use a Dynamic IP</title><link>https://devendevour.iankulin.com/allowing-proxmox-to-use-a-dynamic-ip/</link><pubDate>Thu, 06 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/allowing-proxmox-to-use-a-dynamic-ip/</guid><description>&lt;p&gt;I&amp;rsquo;ve &lt;a href="https://devendevour.iankulin.com/proxmox-dynamic-ip/"&gt;discussed before&lt;/a&gt; , that when you first install Proxmox, it grabs an IP address from your DHCP server (this usually runs in your ISP modem if you haven&amp;rsquo;t created a better setup), but then it stores it as a static ip. This is a sort of compromise that makes sense and works for most circumstances.&lt;/p&gt;
&lt;p&gt;As soon as I&amp;rsquo;ve provisioned a new Proxmox server, I then usually tell the DHCP server, to always serve that address to the MAC address of the new Proxmox server. Since Proxmox does not use the DHCP server on subsequent boots, all that really does is prevent the DHCP server give the same IP address out to another device - which had happened to me prompting the earlier post. The DHCP server had given the address to a wifi lightbulb while the server was off, then when the Proxmox server booted up, the netwrok access was all messed up.&lt;/p&gt;</description></item><item><title>RAID Rescue</title><link>https://devendevour.iankulin.com/raid-rescue/</link><pubDate>Tue, 04 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/raid-rescue/</guid><description>&lt;p&gt;I&amp;rsquo;m in the process of shuffling disks around as I move towards my 3-2-1 storage arrangements. I thought after my extensive rsync adventures I&amp;rsquo;d mirrored everything everywhere, but then realised, with a sinking (no pun) feeling, after I&amp;rsquo;d repurposed a drive out of the 2 drive Synology as a USB caddy drive and wiped it, that I&amp;rsquo;d forgotten my audio book directory. All my rsync fiddling around had been on the video subdirectory of the media folder, not the whole media directory that included my audiobooks.&lt;/p&gt;</description></item><item><title>HP EliteDesk 800 G2 Memory Upgrade</title><link>https://devendevour.iankulin.com/hp-elitedesk-800-g2-memory-upgrade/</link><pubDate>Sun, 02 Apr 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/hp-elitedesk-800-g2-memory-upgrade/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/swellingcomputerbrain_73513374.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The hardware engineering of these corporate world mini-PCs is really nice. I swapped out the RAM today to bump my main machine up to 32GB from 16GB. It was a straightforward task - no screwdrivers, no drama.&lt;/p&gt;
&lt;p&gt;To open the machine up, there is a single large screw on the back that can be undone with your fingers - it&amp;rsquo;s a captive screw, as in it doesn&amp;rsquo;t fall out - just another nice engineering thought.&lt;/p&gt;</description></item><item><title>Proxmox Backup Files</title><link>https://devendevour.iankulin.com/proxmox-backup-files/</link><pubDate>Fri, 31 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/proxmox-backup-files/</guid><description>&lt;p&gt;I&amp;rsquo;ve got some extra RAM to drop into the HP 800 G2 mini that I use as my production server. I feel like that&amp;rsquo;s a low risk change, but since it&amp;rsquo;s easy to take VM snapshots I shutdown the VM&amp;rsquo;s and did that, and wanted to just copy them off the local storage.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m moving towards having these backups (and the ISOs) on the NAS rather than locally, but have not implemented that. So to get my backups I need to SSH in and find them.&lt;/p&gt;</description></item><item><title>rsync episode IV - a sudo hope</title><link>https://devendevour.iankulin.com/rsync-episode-iv-a-sudo-hope/</link><pubDate>Thu, 30 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/rsync-episode-iv-a-sudo-hope/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/imperialofficersworkingatl_62923535.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;With all those earlier rsync bumps out of the way, I was ready to try my first rsync backup at the command line to sync my movies directory on the NAS to a (NTFS formatted) USB drive plugged into the same NAS. This is to be one of the simplest since there&amp;rsquo;s no remote server involved, just copying from mount point directory to another - so no drama with remote permissions.&lt;/p&gt;</description></item><item><title>rsync / Synology / @eaDir</title><link>https://devendevour.iankulin.com/rsync-synology-eadir/</link><pubDate>Tue, 28 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/rsync-synology-eadir/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/extendedattributes_31636167.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The reason I&amp;rsquo;ve been figuring out rsync is to setup my backup strategy. Eventually this will partly be managed with scheduled tasks (ie cron jobs) running rsync. I wanted the SSH in and try this out, since I didn&amp;rsquo;t know some basic things like the mount points of the shares.&lt;/p&gt;
&lt;h3 id="mount-points"&gt;Mount points&lt;/h3&gt; &lt;p&gt;My first issue was to find the paths to all my data. This turned out not to be a drama. Each of the volumes you create when the NAS is set up are just in the root directory. This includes any USB drives plugged in.&lt;/p&gt;</description></item><item><title>SSH with Keys to Synology</title><link>https://devendevour.iankulin.com/ssh-with-keys-to-synology/</link><pubDate>Mon, 27 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ssh-with-keys-to-synology/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/hddlockedup_50825865.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The Synology operating system DSM (I&amp;rsquo;m on DSM 7.1.1) is Linux, but its highly customised for the purpose of making running a complicated Linux NAS doable for less technical users.&lt;/p&gt;
&lt;p&gt;Due to that, some things that are routine in a regular distro, require a few more steps to jump through to get them to work. SSH-ing in to a Synology with keys is one of those things.&lt;/p&gt;
&lt;h3 id="should-you"&gt;Should you?&lt;/h3&gt; &lt;p&gt;Before you do start fiddling around, it&amp;rsquo;s probably worth mentioning that almost all the things you might want to do on the Synology can be accomplished through their web interface, or by installing a &amp;lsquo;package&amp;rsquo; from the &lt;em&gt;Package Center&lt;/em&gt;. For example, if you need to run a cron job, that&amp;rsquo;s done through the &lt;em&gt;Control Panel&lt;/em&gt; &amp;lsquo;&lt;em&gt;Task Scheduler&lt;/em&gt;&amp;rsquo;. If you need TailScale installed to easily access it over Wireguard, there&amp;rsquo;s a TailScale package. In general it&amp;rsquo;s probably easier and safer to do things their way.&lt;/p&gt;</description></item><item><title>rsync basics</title><link>https://devendevour.iankulin.com/rsync-basics/</link><pubDate>Sun, 26 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/rsync-basics/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/computerdatasynchronisation_31637620.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve started down the path of improved storage management, including embracing the &lt;a href="https://www.backblaze.com/blog/the-3-2-1-backup-strategy/" target="_blank" rel="noopener"&gt;3-2-1&lt;/a&gt; mantra. I&amp;rsquo;ve settled on a RAID6 NAS for local, mirrored to an off-site NAS, and an offline local USB drive.&lt;/p&gt;
&lt;p&gt;While I&amp;rsquo;ve been setting those up, my services have been live, so files have been changing on my main storage, which I&amp;rsquo;ve then switched to the bigger NAS, and I&amp;rsquo;ve been trying to keep data in sync by remembering what changes have been made where, and manually replicating them. That&amp;rsquo;s not sustainable and not the plan.&lt;/p&gt;</description></item><item><title>CPU Comparisons</title><link>https://devendevour.iankulin.com/cpu-comparisons/</link><pubDate>Fri, 24 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/cpu-comparisons/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/cloud.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;When I was a young whipper-snapper, working at the &amp;ldquo;data processing&amp;rdquo; centre, you could see if one CPU was better than another one by the CPU name/number. No one wanted an 8086 once the 286&amp;rsquo;s came out. Then a 386 was what you wanted for the latest multitasking support, but only till the 486 was available, then you wanted that for the gargantuan memory addressing.&lt;/p&gt;
&lt;p&gt;With that idea firmly in mind, I&amp;rsquo; wanted an i5 to be better than an i3, and an i7 better than all of them, but it&amp;rsquo;s &lt;a href="https://www.makeuseof.com/tag/compare-different-cpus-right-way/" target="_blank" rel="noopener"&gt;apparently not that simple&lt;/a&gt; . I do come across people in forums talking about &amp;lsquo;generations&amp;rsquo; of Intel processors - so all this is probably decodable, but I&amp;rsquo;m not exactly sure how.&lt;/p&gt;</description></item><item><title>HP Secure Boot Pain</title><link>https://devendevour.iankulin.com/hp-secure-boot-pain/</link><pubDate>Thu, 23 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/hp-secure-boot-pain/</guid><description>&lt;p&gt;Since the HP EliteDesk 800 G1 I&amp;rsquo;m using as a dev/homelab machine is going to be re-purposed as a media/backup server elsewhere, I&amp;rsquo;ve grabbed another G2 to use as a second box. The homelab machine serves as a backup device for the production server that runs my self-hosted services, but also is the machine I play with - testing my software, but also trying out any new self-hosted software I&amp;rsquo;m having a look out or configurarions I&amp;rsquo;m thinking about for the &amp;lsquo;production&amp;rsquo; server.&lt;/p&gt;</description></item><item><title>Mounting one Synology NAS to another one</title><link>https://devendevour.iankulin.com/mounting-one-synology-nas-to-another-one/</link><pubDate>Tue, 21 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/mounting-one-synology-nas-to-another-one/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_4344.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I went over mounting a Synology NAS share on a Mac or Linux host &lt;a href="https://devendevour.iankulin.com/accessing-a-synology-nas-from-linux/"&gt;a while ago&lt;/a&gt; . Now I&amp;rsquo;ve populated a new NAS, and I want to copy my data over to it. I could mount them both to my laptop, and the data flow would look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;NAS1 - switch - wifi - laptop - wifi - switch - NAS2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since I&amp;rsquo;m copying 4TB, it will take a few hours, and if I forget what&amp;rsquo;s going on and close the laptop, or take it outside of my wifi the transfer will die, and I won&amp;rsquo;t be sure which files are patent. What might be better would be something like this:&lt;/p&gt;</description></item><item><title>Proxmox VM Memory Upgrade</title><link>https://devendevour.iankulin.com/proxmox-vm-memory-upgrade/</link><pubDate>Sun, 19 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/proxmox-vm-memory-upgrade/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-03-16-at-6.36.10-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I ordered some RAM this week for my production server - it&amp;rsquo;s quickly becoming clear that memory is the limiting factor when running lots of services and VM&amp;rsquo;s that don&amp;rsquo;t get much use - rather than processing power. I&amp;rsquo;m not really a hardware guy, so figuring out exactly what RAM I need is a slightly fraught process - I won&amp;rsquo;t be fully confident I&amp;rsquo;ve ordered the right thing until I install it, boot up, and see my &lt;a href="https://support.hp.com/us-en/product/hp-elitedesk-800-35w-g2-desktop-mini-pc/7633266/document/c04816235" target="_blank" rel="noopener"&gt;G2 800&lt;/a&gt; come to life maxed out at 32GB.&lt;/p&gt;</description></item><item><title>No DNS on Proxmox machine</title><link>https://devendevour.iankulin.com/no-dns-on-proxmox-machine/</link><pubDate>Fri, 17 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/no-dns-on-proxmox-machine/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/confusedanimegirl_40917951.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I had some more network weirdness setting up this new Proxmox machine. When I went to run the updates it couldn&amp;rsquo;t resolve any of the addresses:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@pve-kr01:~# apt update
Err:1 http://ftp.au.debian.org/debian bullseye InRelease
 Temporary failure resolving &amp;#39;ftp.au.debian.org&amp;#39;
Err:2 http://download.proxmox.com/debian/pve bullseye InRelease
 Temporary failure resolving &amp;#39;download.proxmox.com&amp;#39;
Err:3 http://security.debian.org bullseye-security InRelease
 Temporary failure resolving &amp;#39;security.debian.org&amp;#39;
Err:4 https://enterprise.proxmox.com/debian/pve bullseye InRelease
 Temporary failure resolving &amp;#39;enterprise.proxmox.com&amp;#39;
Err:5 http://ftp.au.debian.org/debian bullseye-updates InRelease
 Temporary failure resolving &amp;#39;ftp.au.debian.org&amp;#39;
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
All packages are up to date.
W: Failed to fetch http://ftp.au.debian.org/debian/dists/bullseye/InRelease Temporary failure resolving &amp;#39;ftp.au.debian.org&amp;#39;
W: Failed to fetch http://ftp.au.debian.org/debian/dists/bullseye-updates/InRelease Temporary failure resolving &amp;#39;ftp.au.debian.org&amp;#39;
W: Failed to fetch http://download.proxmox.com/debian/pve/dists/bullseye/InRelease Temporary failure resolving &amp;#39;download.proxmox.com&amp;#39;
W: Failed to fetch http://security.debian.org/dists/bullseye-security/InRelease Temporary failure resolving &amp;#39;security.debian.org&amp;#39;
W: Failed to fetch https://enterprise.proxmox.com/debian/pve/dists/bullseye/InRelease Temporary failure resolving &amp;#39;enterprise.proxmox.com&amp;#39;
W: Some index files failed to download. They have been ignored, or old ones used instead.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So some sort of DNS problem. The entry for the DNS is in &lt;code&gt;/etc/resolv.conf&lt;/code&gt; when I looked in there, it said:&lt;/p&gt;</description></item><item><title>Proxmox Dynamic IP</title><link>https://devendevour.iankulin.com/proxmox-dynamic-ip/</link><pubDate>Thu, 16 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/proxmox-dynamic-ip/</guid><description>&lt;p&gt;I ran into a little hiccup today. I&amp;rsquo;m building out a Jellyfin media server in a little HP G2 Mini PC. The config was going to be a Debian server inside Proxmox (because I love VM snapshots for backups) running Jellyfin in a container. There&amp;rsquo;ll be an external USB3 hard drive for the media storage.&lt;/p&gt;
&lt;p&gt;I was intending to build it all out and test it, then ship it to it&amp;rsquo;s final home.&lt;/p&gt;</description></item><item><title>Nostalgia</title><link>https://devendevour.iankulin.com/nostalgia/</link><pubDate>Tue, 14 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/nostalgia/</guid><description>&lt;p&gt;I&amp;rsquo;m not super interested in FreeDOS, but did enjoy this video from Jim Hall since I lived through all this, and was working in IT (well, &amp;lsquo;data processing&amp;rsquo; actually) during the introduction of the IBM PC.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/3E5Hog5OnIM?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;My first DOS was 2.11, but spent a lot more time on 3.12, and later 4.01. Windows wasn&amp;rsquo;t really ready for anyone until 3.1 which is when I dived in there. I seem to remember purchasing a PC with a whole megabyte of RAM in anticipation!&lt;/p&gt;</description></item><item><title>NAS Storage Calculations</title><link>https://devendevour.iankulin.com/nas-storage-calculations/</link><pubDate>Sat, 11 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/nas-storage-calculations/</guid><description>&lt;p&gt;I&amp;rsquo;ve been really happy with my two bay Synology NAS - a DS216j. The Synology&amp;rsquo;s seem to have great reputation for just pushing on. Mine is loaded up with two 8TB Seagate Barracudas in RAID 1 leaving me with a one drive failure redundancy.&lt;/p&gt;
&lt;p&gt;I guess a more hard-core host-er than me would be building their own array and using Unraid or ZFS or something. I&amp;rsquo;m pretty comfortable with the Synology off the shelf system; it&amp;rsquo;s a good match for my (low) level of expertise, and more robust than my previous storage system of a USB external drive.&lt;/p&gt;</description></item><item><title>Recursive list of files in Linux</title><link>https://devendevour.iankulin.com/recursive-list-of-files-in-linux/</link><pubDate>Wed, 08 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/recursive-list-of-files-in-linux/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/macro094_recursive_feedback_loops_7129c6ad-a42c-4698-b3d7-bf340201b6ac.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve spent a few hours over the weekend migrating a media library from an external USB drive to the NAS, and in the process reorganised it, and in many cases bulk changed file names. I&amp;rsquo;ve also added a heap of metadata.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like to check that I haven&amp;rsquo;t missed any files, but a side by side listing of each data source won&amp;rsquo;t do the trick, so I&amp;rsquo;ll probably end up pulling the data into a spreadsheet, but I&amp;rsquo;d like to get as close as possible with Linux-fu first.&lt;/p&gt;</description></item><item><title>Could it be a permissions problem?</title><link>https://devendevour.iankulin.com/could-it-be-a-permissions-problem/</link><pubDate>Sun, 05 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/could-it-be-a-permissions-problem/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/padlock.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Unix, and therefore Linux, was built from the ground up as a multi-user system. Thanks to this, great security is baked in, for example every file has permission attributes for it&amp;rsquo;s owner, the group the owner is a member of, and then everyone. For example, it might be a good idea if I can read, write and execute my own files, but the other members of my group can just read them, and any other user on the system has none of those rights.&lt;/p&gt;</description></item><item><title>Problems mounting network share at boot</title><link>https://devendevour.iankulin.com/problems-mounting-network-share-at-boot/</link><pubDate>Wed, 01 Mar 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/problems-mounting-network-share-at-boot/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_pretty_woman_putting_boots_on_comic_style_4590f478-67f8-4f8d-8e42-e38b9e059f37.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I had Jellyfin working nicely in an LXC container in Proxmox, but could not get Tailscale working in the container. Since this is going to be an important part of accessing my media away from home, I decided it was probably worth the extra bulk to run JellyFin in a VM.&lt;/p&gt;
&lt;p&gt;Following &lt;a href="https://devendevour.iankulin.com/accessing-a-synology-nas-from-linux/"&gt;my own instructions&lt;/a&gt; , I had the mount command in the /etc/fstab file so it would persist across reboots. It looked a bit like this:&lt;/p&gt;</description></item><item><title>Sudoers' file not working</title><link>https://devendevour.iankulin.com/sudoers-file-not-working/</link><pubDate>Mon, 27 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/sudoers-file-not-working/</guid><description>&lt;p&gt;A couple of weeks ago, I posted &lt;a href="https://devendevour.iankulin.com/sudo-incident-reports-where-do-they-go/"&gt;about the sudoers&amp;rsquo; file&lt;/a&gt; , and how there was a special tool for editing it since breaking it is a bad idea, and that in fact I needn&amp;rsquo;t bother, since I can just add my user to the sudoers&amp;rsquo; group with:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;usermod -a -G sudo ian
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That worked (on Unbuntu) since &lt;code&gt;/etc/sudoers&lt;/code&gt; contained a line saying:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Allow members of group sudo to execute any command
%sudo	ALL=(ALL:ALL) ALL
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I tried the same trick on a fresh Debian install today, and no dice:&lt;/p&gt;</description></item><item><title>Folder ownership problems with Jellyfin</title><link>https://devendevour.iankulin.com/folder-ownership-problems-with-jellyfin/</link><pubDate>Wed, 22 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/folder-ownership-problems-with-jellyfin/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-02-18-at-5.32.36-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;After being so blase about the file permissions when mounting the share to the Linux file system, and testing that root could read and write to the share, I ran into problems immediately when trying to add the media folder as a library in Jellyfin - getting the error &amp;ldquo;The path could not be found. Please ensure the path is valid and try again.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I definitely had the path correct - I could copy it from the dialog and cd to it at the CLI. So I suspected it was a permissions thing. The app might not have read permissions for the directory.&lt;/p&gt;</description></item><item><title>Accessing a Synology NAS from Linux</title><link>https://devendevour.iankulin.com/accessing-a-synology-nas-from-linux/</link><pubDate>Mon, 20 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/accessing-a-synology-nas-from-linux/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_4154x.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I picked up a Synology DS216j NAS from eBay to use for storage for the rapidly growing home lab. The eventual plan is that as well as my VM backups, it will host the media library, and eventually (when this has all proved itself reasonably bullet-proof) my current DropBox contents. That won&amp;rsquo;t all fit on the 2x2TB drives that the DS216j came with, and I have a pair of 8TBs on hand, but I wanted to set it up and checked it all worked.&lt;/p&gt;</description></item><item><title>External USB Drives in Linux</title><link>https://devendevour.iankulin.com/external-usb-drives-in-linux/</link><pubDate>Sat, 18 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/external-usb-drives-in-linux/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_32gb_usb_drive_with_tiny_construction_workers_around_it_f4862ea6-8d0d-48f9-a2bb-7284240f151d.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Many modern Linux distros will auto-mount USB drives - they just pop up in the graphical file manager as users would expect. When you&amp;rsquo;re running server, older, or smaller versions, that&amp;rsquo;s probably not going to be the case, and you&amp;rsquo;ll have to do it old school.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at some basics. &lt;code&gt;[lsblk](https://man7.org/linux/man-pages/man8/lsblk.8.html)&lt;/code&gt; will list the &amp;lsquo;block&amp;rsquo; devices. Your output will almost certainly be a bit different than this.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@pve:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 119.2G 0 disk 
├─sda1 8:1 0 1007K 0 part 
├─sda2 8:2 0 512M 0 part /boot/efi
└─sda3 8:3 0 118.7G 0 part 
 ├─pve-swap 253:0 0 7.7G 0 lvm [SWAP]
 ├─pve-root 253:1 0 39.8G 0 lvm /
 ├─pve-data_tmeta 253:2 0 1G 0 lvm 
 │ └─pve-data-tpool 253:4 0 54.6G 0 lvm 
 │ ├─pve-data 253:5 0 54.6G 1 lvm 
 │ ├─pve-vm--100--disk--0 253:6 0 10G 0 lvm 
 │ ├─pve-vm--101--disk--0 253:7 0 10G 0 lvm 
 │ ├─pve-vm--300--disk--0 253:8 0 8G 0 lvm 
 │ ├─pve-vm--102--disk--0 253:9 0 4M 0 lvm 
 │ └─pve-vm--102--disk--1 253:10 0 32G 0 lvm 
 └─pve-data_tdata 253:3 0 54.6G 0 lvm 
 └─pve-data-tpool 253:4 0 54.6G 0 lvm 
 ├─pve-data 253:5 0 54.6G 1 lvm 
 ├─pve-vm--100--disk--0 253:6 0 10G 0 lvm 
 ├─pve-vm--101--disk--0 253:7 0 10G 0 lvm 
 ├─pve-vm--300--disk--0 253:8 0 8G 0 lvm 
 ├─pve-vm--102--disk--0 253:9 0 4M 0 lvm 
 └─pve-vm--102--disk--1 253:10 0 32G 0 lvm 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you look at the &lt;code&gt;type&lt;/code&gt; column, you can see this machine has one &lt;em&gt;disk&lt;/em&gt;, with three &lt;em&gt;partitions&lt;/em&gt;, and the last partition has a heap of &lt;em&gt;logical volumes&lt;/em&gt;. Let&amp;rsquo;s plug the thumb drive in:&lt;/p&gt;</description></item><item><title>Configuring Proxmox for Free Use</title><link>https://devendevour.iankulin.com/configuring-proxmox-for-free-use/</link><pubDate>Thu, 16 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/configuring-proxmox-for-free-use/</guid><description>&lt;p&gt;I installed Proxmox on my second server last night, and tonight when I ran &lt;code&gt;apt update&lt;/code&gt; I ran into the error you get when you haven&amp;rsquo;t bought a license.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Err:5 https://enterprise.proxmox.com/debian/pve bullseye InRelease 
 401 Unauthorized [IP: 103.67.14.50 443]
Reading package lists... Done 
E: Failed to fetch https://enterprise.proxmox.com/debian/pve/dists/bullseye/InRelease 401 Unauthorized [IP: 103.67.14.50 443]
E: The repository &amp;#39;https://enterprise.proxmox.com/debian/pve bullseye InRelease&amp;#39; is not signed.
N: Updating from such a repository can&amp;#39;t be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Even though I guess it was only a month ago (let that sink in people who think the raspberry Pi they just bought is going to be the last homelab hardware they buy 😊) since I set up my first Proxmox server, I&amp;rsquo;d already forgotten there&amp;rsquo;s a step to enable it to get updates without a subscription.&lt;/p&gt;</description></item><item><title>Moving a VM between two Proxmox hosts</title><link>https://devendevour.iankulin.com/moving-a-vm-between-two-proxmox-hosts/</link><pubDate>Thu, 16 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/moving-a-vm-between-two-proxmox-hosts/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/s-l640.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;So, the very small datacentre has undergone a major hardware upgrade today. The HP 800 G1 is joined by an HP 800 G2. Four core i7 vs the old two core i5. Double the RAM to 16GB, four times the disk. The old machine will become a dev/play machine - still virtualised, and the new machine will run the production apps, mostly in Docker containers.&lt;/p&gt;
&lt;p&gt;Since everything is containerised, I did consider running Unbuntu Server on the bare metal of the new machine, but running it on Proxmox will give me some flexibility, and since we&amp;rsquo;ve stepped up the underlying hardware resource so substantially, performance will be well in front anyway. Plus it will give me some flexibility if needed in the future.&lt;/p&gt;</description></item><item><title>Uptime Kuma &amp;amp; NFTY</title><link>https://devendevour.iankulin.com/uptime-kuma-nfty/</link><pubDate>Wed, 15 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/uptime-kuma-nfty/</guid><description>&lt;p&gt;&lt;a href="https://github.com/louislam/uptime-kuma" target="_blank" rel="noopener"&gt;Uptime Kuma&lt;/a&gt; is a monitoring tool suitable for self-hosting, and as well as being a good tool for monitoring the status of your network and applications, it&amp;rsquo;s a nice smallish app to get started on Docker containers.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-02-05-at-6.41.24-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Since it&amp;rsquo;s in a container, you need to create a volume for it and pass it in to persist your settings. Then it&amp;rsquo;s just a matter of adding each item you want to monitor. There&amp;rsquo;s a heap of fancy options for this, the only three I&amp;rsquo;ve used are ping - just pings an address, http(s) - requests a page and checks the header for a 200, and http(s) keyword - looks at the returned page for a keyword in the html.&lt;/p&gt;</description></item><item><title>Netgear GS108E switch problem</title><link>https://devendevour.iankulin.com/netgear-gs108e-switch-problem/</link><pubDate>Tue, 14 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/netgear-gs108e-switch-problem/</guid><description>&lt;p&gt;I had a weird issue today that I wouldn&amp;rsquo;t have known about if I didn&amp;rsquo;t have an over-engineered home network monitoring system.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got a new &lt;a href="https://www.netgear.com/au/business/wired/switches/plus/gs108e/" target="_blank" rel="noopener"&gt;GS108E managed switch&lt;/a&gt; , purchased in anticipation of connecting a NAS to the homelab - I want to have a solid 1Gb connection between the NAS and the servers, and also in anticipation of moving to VLANs before I start to expose self-hosted services to the internet.&lt;/p&gt;</description></item><item><title>Local host names with Pi-hole</title><link>https://devendevour.iankulin.com/local-host-names-with-pi-hole/</link><pubDate>Mon, 13 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/local-host-names-with-pi-hole/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-02-04-at-5.46.22-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I run an instance of Pi-hole as a network-wide advert and surveillance blocker. It also has a setting to block individual domain which I use to force myself to really consider if 30 minutes of &lt;a href="https://old.reddit.com/r/homelab/" target="_blank" rel="noopener"&gt;Reddit&lt;/a&gt; is a good idea when I should probably just be going to bed.&lt;/p&gt;
&lt;p&gt;As I&amp;rsquo;ve increased the number of real and virtual devices on my network, it&amp;rsquo;s getting to be a pain remembering all of their IP addresses. So I&amp;rsquo;d like to have DNS entries for them, for example I&amp;rsquo;d much rather:&lt;/p&gt;</description></item><item><title>ssh key login on VPS</title><link>https://devendevour.iankulin.com/ssh-key-login-on-vps/</link><pubDate>Sun, 12 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ssh-key-login-on-vps/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_side_view_of_a_female_knight_walking_up_to_a_castle_door_645ac316-6393-4e33-8199-36bf31d88b53.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Due to &lt;a href="https://devendevour.iankulin.com/chinese-hackers-want-to-steal-my-hello-world-container/"&gt;potential brute force attacks&lt;/a&gt; , it&amp;rsquo;s a good idea to turn off password access via shh and instead rely on ssh keys. In this post, I&amp;rsquo;ll run through that process.&lt;/p&gt;
&lt;h4 id="generating-your-key"&gt;Generating your key&lt;/h4&gt; &lt;p&gt;On a mac (or actually most *ix systems), your ssh keys live in the &lt;code&gt;.ssh&lt;/code&gt; directory inside the users home directory. Since it starts with a period, it&amp;rsquo;s a &amp;lsquo;hidden&amp;rsquo; directory. To see it in Finder press&lt;/p&gt;</description></item><item><title>Save Proxmox password in Chrome</title><link>https://devendevour.iankulin.com/save-proxmox-password-in-chrome/</link><pubDate>Sat, 11 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/save-proxmox-password-in-chrome/</guid><description>&lt;p&gt;When I installed Proxmox, I&amp;rsquo;d used a secure, and therefore absurdly long and complicated root password. I do use a password manager, but don&amp;rsquo;t have it integrated into Chrome, so it was buggging me having to find it and paste it in each time - why wasn&amp;rsquo;t Chrome offering to save it for me?&lt;/p&gt;
&lt;p&gt;Well, you&amp;rsquo;d guess it was something to do with this. I feel like Chrome is trying to tell me something here:&lt;/p&gt;</description></item><item><title>Saved by the qemu_guest_agent</title><link>https://devendevour.iankulin.com/saved-by-the-qemu_guest_agent/</link><pubDate>Fri, 10 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/saved-by-the-qemu_guest_agent/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_photo_of_female_cyborg_holding_a_small_child_in_her_arms_ac9cb085-3dd4-444b-8a0c-6dafc5b48418.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Literally an hour after I wrote the post &lt;a href="https://devendevour.iankulin.com/proxmox-qemu-guest-agent/"&gt;about installing the qemu guest agent&lt;/a&gt; in a VM and explaining how it can be used to inject root level commands into a VM, I had use of it due to a mistake.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d decided to add myself to the sudoers file. Since the last line in that file is a directive to include all the files in the /etc/sudoers.d directory, the accepted way to do that for local changes is to create a file in that directory with the necessary commands.&lt;/p&gt;</description></item><item><title>Proxmox - Qemu-guest-agent</title><link>https://devendevour.iankulin.com/proxmox-qemu-guest-agent/</link><pubDate>Thu, 09 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/proxmox-qemu-guest-agent/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_large_stone_wall_with_a_crack_of_sunlight_shining_throug_b2b090d2-7855-4170-9c5c-a899b205668d.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;One of the strengths of having virtual machines (VMs) running inside a hypervisor like Proxmox is how they are isolated from each other and their host. This is a strength - if there is a problem with a particular VM nothing else should be affected by it.&lt;/p&gt;
&lt;p&gt;But this can also be a pain if the hypervisor needs access to a VM to control or monitor it in some way that&amp;rsquo;s only possible from inside the VM. Proxmox can use the &lt;a href="https://qemu-project.gitlab.io/qemu/interop/qemu-ga.html" target="_blank" rel="noopener"&gt;Qemu Guest Agent&lt;/a&gt; for this purpose. To over simplify, this is a deamon that runs in the VM and opens a unix socket/virtual serial port to the hypervisor, and listens for commands on it. With Proxmox, the main use of this is to aid in orderly shutdowns and backups, but it also allows us to run commands in the VM from Proxmox - an obvious security compromise. You definitely would not want to install this daemon on a hosted VPS.&lt;/p&gt;</description></item><item><title>SSH &amp;amp; the scary warning</title><link>https://devendevour.iankulin.com/ssh-the-scary-warning/</link><pubDate>Wed, 08 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ssh-the-scary-warning/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-01-28-at-8.41.11-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The first time you connect to a new server with ssh, it asks you something like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;➜ ~ &amp;gt; ssh ian@192.168.100.20 
The authenticity of host &amp;#39;192.168.100.20 (192.168.100.20)&amp;#39; can&amp;#39;t be established.
ED25519 key fingerprint is SHA256:ZcNTcOjO/0fOLC5iNChf8Q8MHN7z2d+VV0qz7XqH1g4.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added &amp;#39;192.168.100.20&amp;#39; (ED25519) to the list of known hosts.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once you&amp;rsquo;ve said yes, it adds the server &amp;lsquo;fingerprint&amp;rsquo; to the known hosts file, then next time you ssh there, it feels safe - we know this server.&lt;/p&gt;</description></item><item><title>Proxmox - Installing a Virtual Machine</title><link>https://devendevour.iankulin.com/proxmox-installing-a-virtual-machine/</link><pubDate>Tue, 07 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/proxmox-installing-a-virtual-machine/</guid><description>&lt;p&gt;Installing your first virtual machine (VM) in the Proxmox hypervisor is pretty straightforward. This post runs through those steps using Proxmox 7.3.&lt;/p&gt;
&lt;p&gt;You need an operating system for your virtual machine, I&amp;rsquo;m going to use &lt;a href="https://ubuntu.com/download/server" target="_blank" rel="noopener"&gt;Ubuntu server&lt;/a&gt; in this example, but it could just as easily be &lt;a href="https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2016-essentials" target="_blank" rel="noopener"&gt;Windows server&lt;/a&gt; , or regular windows, or one of the desktop Linux distributions. Whichever you decide, you&amp;rsquo;ll need to find and download the ISO for it. The ISO is a (usually quite large) file needed to install the operating system.&lt;/p&gt;</description></item><item><title>Chinese Hackers Want to steal my Hello World container</title><link>https://devendevour.iankulin.com/chinese-hackers-want-to-steal-my-hello-world-container/</link><pubDate>Mon, 06 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/chinese-hackers-want-to-steal-my-hello-world-container/</guid><description>&lt;p&gt;A smart thing to do after setting up a server on the internet, is to set up SSH keys and then turn passwords off for SSH. The reason for this is that scanning for open port 22 on IP addresses, then brute forcing password files on them is pretty much hacker 101. So if you have passwords turned on, and especially if you have a weak password you are really inviting someone to take over your server as root and add it to their botnet army for liking Putin&amp;rsquo;s twitter posts or whatever.&lt;/p&gt;</description></item><item><title>Your own Aussie server on BinaryLane</title><link>https://devendevour.iankulin.com/your-own-aussie-server-on-binarylane/</link><pubDate>Sun, 05 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/your-own-aussie-server-on-binarylane/</guid><description>&lt;p&gt;Listening to podcasts, I&amp;rsquo;ve been jealous of US developers who seem to have masses of $5/month VPS (Virtual Private Server) options. When I looked for similar Australian offerings a few months ago, they all seem to start at around $35 which is outside of my &amp;lsquo;have a play with something&amp;rsquo; budget range.&lt;/p&gt;
&lt;p&gt;I could of course use one of the international options, but one of the main apps on my app ideas list needs to be hosted in Australia and work under Australian data privacy rules. That might be the case for Digital Ocean (or other US companies) if you select an AU server, but I&amp;rsquo;m not a lawyer. For the imaginary clients of my imaginary app, me being able to say that the hosting is with an Australian company in Australia would be a plus.&lt;/p&gt;</description></item><item><title>sudo Incident Reports - where do they go?</title><link>https://devendevour.iankulin.com/sudo-incident-reports-where-do-they-go/</link><pubDate>Sat, 04 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/sudo-incident-reports-where-do-they-go/</guid><description>&lt;p&gt;Even though it&amp;rsquo;s &lt;em&gt;my&lt;/em&gt; server, I still have a pang of guilt when this happens.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-01-28-at-10.40.43-am-copy.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I always imagine &lt;a href="https://en.wikipedia.org/wiki/Richard_Stallman" target="_blank" rel="noopener"&gt;Richard Stallman&lt;/a&gt; (or someone with a similar 2000&amp;rsquo;s database administrator beard) looking at me disappointedly and shaking his head slowly.&lt;/p&gt;
&lt;p&gt;It does raise the question though - since it&amp;rsquo;s my server, shouldn&amp;rsquo;t I be getting a text message from CERN or something?&lt;/p&gt;
&lt;h4 id="where-is-this-report"&gt;Where is this report?&lt;/h4&gt; &lt;p&gt;(&lt;a href="https://xkcd.com/838/" target="_blank" rel="noopener"&gt;Relevant xkcd&lt;/a&gt; )&lt;/p&gt;
&lt;p&gt;Like everything, the answer is &amp;lsquo;it&amp;rsquo;s logged&amp;rsquo;. We can use the &lt;code&gt;journalctl&lt;/code&gt; command to look at the logs, on this server that&amp;rsquo;s been running less than 20 hours, there&amp;rsquo;s already several thousand lines to look through if you just enter &lt;code&gt;journalctl&lt;/code&gt;, so I&amp;rsquo;m going to just send all the high priority logs to a file:&lt;/p&gt;</description></item><item><title>Proxmox - Storage Basics</title><link>https://devendevour.iankulin.com/proxmox-storage-basics/</link><pubDate>Fri, 03 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/proxmox-storage-basics/</guid><description>&lt;p&gt;Once you&amp;rsquo;ve got Proxmox installed, you can point your web browser at the IP for the physical server, and use the port 8006. Log in as &lt;code&gt;root&lt;/code&gt; using the password you entered during the install. If you just accepted all the defaults during the install it will look something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-01-26-at-7.52.16-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s discuss what you&amp;rsquo;re seeing in that &amp;lsquo;Server View&amp;rsquo; on the left there. &lt;code&gt;pve&lt;/code&gt; is the name of my &lt;em&gt;node&lt;/em&gt; - this installation of Proxmox on my physical server. If you named your server something different during the install, it will be show that name here.&lt;/p&gt;</description></item><item><title>Upgrade Cycle</title><link>https://devendevour.iankulin.com/upgrade-cycle/</link><pubDate>Thu, 02 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/upgrade-cycle/</guid><description>&lt;p&gt;Now that I&amp;rsquo;ve seen I can easily stand up VM&amp;rsquo;s on this baby server, it&amp;rsquo;s apparent the first limitation I&amp;rsquo;ll run into is RAM. It has two laptop sized memory slots that can take up to 8GB apiece. So it could easily be doubled, but at a cost of around $70.&lt;/p&gt;
&lt;p&gt;While I&amp;rsquo;m looking on eBay for RAM, the algorithm thinks I might be interested in this.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-01-26-at-3.47.47-pm-2.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/upgrades.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;While I&amp;rsquo;m looking at the specs (4 cores - the current one has 2, double the RAM, bigger disk), eBay is like &amp;ldquo;Hey, how about this 20% off discount code - is thAt soMetHing ThAt miGHt HeLp yoU deCiDe?&amp;rdquo;&lt;/p&gt;</description></item><item><title>Proxmox Hypervisor</title><link>https://devendevour.iankulin.com/proxmox-hypervisor/</link><pubDate>Wed, 01 Feb 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/proxmox-hypervisor/</guid><description>&lt;p&gt;I &lt;a href="https://devendevour.iankulin.com/pi-server/"&gt;mentioned a while ago&lt;/a&gt; that the price of the &lt;a href="https://www.raspberrypi.com/products/raspberry-pi-4-model-b/specifications/" target="_blank" rel="noopener"&gt;Raspberry Pi4&lt;/a&gt; was getting such that it&amp;rsquo;s smarter to purchase one of the little business workstations instead. Depsite having little need for such a thing, I went ahead and bought an &lt;a href="https://support.hp.com/au-en/document/c04266271" target="_blank" rel="noopener"&gt;HP Elitedesk 800 G1&lt;/a&gt; &amp;ldquo;mini&amp;rdquo; PC. It has 8GB RAM (which is the max for the Pi4) as well as a 128GB SDD, the processor is an Intel i5.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-01-26-at-10.54.25-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;This compares pretty well with the 8GB Pi4 which only has a fraction of the storage (on an SD card) at around $400. One area where the Pi would have an edge might be in power consumption - I expect it would be a bit less. One possible catch for young players is that the HP has a &amp;lsquo;display port&amp;rsquo; rather than HDMI for the screen connection, so pick up a $5 adapter if you&amp;rsquo;re getting one. The metal case and nice finishing on the HP actually looks really great in my office compared with my Pi 3b+ dev server that&amp;rsquo;s sort of hanging on the end of a cat5 cable.&lt;/p&gt;</description></item><item><title>Expired Packages Part II</title><link>https://devendevour.iankulin.com/expired-packages-part-ii/</link><pubDate>Tue, 31 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/expired-packages-part-ii/</guid><description>&lt;p&gt;Following on from the previous post&amp;hellip;&lt;/p&gt;
&lt;p&gt;I went the nuclear route - deleted the node_modules folder, package-lock.json and installed the packages from packages.json. I still had some errors, but the react app at least ran correctly. Also, the messages are a bit more intelligible, and all of them cascade from this one.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# npm audit report

nth-check &amp;lt;2.0.1
Severity: high
Inefficient Regular Expression Complexity in nth-check - https://github.com/advisories/GHSA-rp65-9cf3-cjxr
fix available via `npm audit fix --force`
Will install react-scripts@2.1.3, which is a breaking change
node_modules/svgo/node_modules/nth-check
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From my, admittedly ignorant, viewpoint, there&amp;rsquo;s a couple of weird things going on here.&lt;/p&gt;</description></item><item><title>Expired packages</title><link>https://devendevour.iankulin.com/expired-packages/</link><pubDate>Mon, 30 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/expired-packages/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_a_large_pile_of_garbage_containg_binary_code_slowly_rott_55771f9e-3781-42e7-90f0-e6943b12ae8e-1.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;At several points in the &lt;a href="https://www.udemy.com/course/the-complete-web-developer-zero-to-mastery/" target="_blank" rel="noopener"&gt;Complete Web Developer&lt;/a&gt; course, deprecated packages have been used, with the slide before the video explaining what&amp;rsquo;s happening, and giving a work around, or sometimes - as is the case for the bit I&amp;rsquo;m just starting - exhorting the benefits of dropping you into a non-working mess and having you figure it out yourself.&lt;/p&gt;
&lt;p&gt;While this argument can be reasonably made - that figuring things out on your own is a valuable skill - it&amp;rsquo;s also a useful fig leaf to cover up the fact that they haven&amp;rsquo;t bothered to fix the course to make it work out of the box.&lt;/p&gt;</description></item><item><title>CodePen</title><link>https://devendevour.iankulin.com/codepen/</link><pubDate>Sun, 29 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/codepen/</guid><description>&lt;p&gt;I think I&amp;rsquo;ve written about CodePen before, its a site that allows users to quickly put together HTML, CSS &amp;amp; JS and see the results as they edit. Users &amp;lsquo;pens&amp;rsquo; are public and can be tagged, so it also serves as a repository of examples.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s possible to host incredibly complex artefacts, such as this &lt;a href="https://codepen.io/ricardoolivaalonso/pen/RwBZMGB" target="_blank" rel="noopener"&gt;3D Sony Walkman&lt;/a&gt; , but what I mostly use it for is to work out simple things - like how to &lt;a href="https://codepen.io/IanKulin/pen/wvxrZxW" target="_blank" rel="noopener"&gt;collapse a row of text into a column&lt;/a&gt; with a media query.&lt;/p&gt;</description></item><item><title>Using the Community</title><link>https://devendevour.iankulin.com/using-the-community/</link><pubDate>Fri, 27 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/using-the-community/</guid><description>&lt;p&gt;You can&amp;rsquo;t always successfully google problems when you&amp;rsquo;re starting out - usually because you don&amp;rsquo;t know the correct terminology for the issue or solution. Often you might still get a newbie StackOverflow hit, but when there&amp;rsquo;s not even that, you need a human to help out.&lt;/p&gt;
&lt;p&gt;One of the things &lt;a href="https://zerotomastery.io/" target="_blank" rel="noopener"&gt;ZTM&lt;/a&gt; do with their courses is to have a Discord based community, then set tasks to encourage it&amp;rsquo;s use - for example one of the exercises I&amp;rsquo;ve already had was to go there and answer a question. Earlier ones were to introduce yourself and to find a partner to work with - both of which would have forced anyone not used to Discord to figure it out.&lt;/p&gt;</description></item><item><title>Openlayers &amp;amp; Vite</title><link>https://devendevour.iankulin.com/openlayers-vite/</link><pubDate>Thu, 26 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/openlayers-vite/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_drawing_of_a_nymph_who_is_looking_through_a_hole_in_a_br_173a47fc-4d46-481f-ae16-7d93381c8296.jpg" alt="drawing of a nymph who is looking through a hole in a brick wall to a beautiful garden - MidJourney" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;In Randy Pausch&amp;rsquo;s &lt;a href="https://www.youtube.com/watch?v=ji5_MqicxSo" target="_blank" rel="noopener"&gt;last lecture&lt;/a&gt; he talks about the benefit of brick walls in our lives - they tell us how much we really want something. Software development is full of these brick walls - things we want to do, but there&amp;rsquo;s a barrier to achieving it. Will we persevere and accomplish the thing, give up, or some other compromise.&lt;/p&gt;</description></item><item><title>APIs - http &amp;amp; https Mixed Content error</title><link>https://devendevour.iankulin.com/apis-http-https-mixed-content-error/</link><pubDate>Tue, 24 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/apis-http-https-mixed-content-error/</guid><description>&lt;p&gt;&amp;lt;img src=&amp;quot;/images/screen-shot-2023-01-16-at-4.45.53-pm.jpg alt=&amp;ldquo;Mixed Content: The page at &amp;lsquo;&lt;URL&gt;&amp;rsquo; was loaded over HTTPS, but requested an insecure resource '&lt;/p&gt;
&lt;p&gt;Ran into a little bump today - I was calling a &lt;a href="http://open-notify.org/Open-Notify-API/ISS-Location-Now/" target="_blank" rel="noopener"&gt;cool API&lt;/a&gt; that gives the current location of the International Space Station. In a classic case of &amp;ldquo;it worked on my machine&amp;rdquo; it worked perfectly in the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer" target="_blank" rel="noopener"&gt;Live server&lt;/a&gt; in VS Code on my laptop, but when I pushed it up to my GitHub space, it didn&amp;rsquo;t work - throwing the error:&lt;/p&gt;</description></item><item><title>React code is not HTML</title><link>https://devendevour.iankulin.com/react-code-is-not-html/</link><pubDate>Sun, 22 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/react-code-is-not-html/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_the_react_atom_logo_fighting_with_some_html_4b0d18eb-b17f-471f-a42c-a99e160b1231-copy.jpg" alt="The React atom logo fighting with some HTML - midjourney, edited" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I was looking at this ugly code in a React app:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;div style={{overflow: &amp;#39;scroll&amp;#39;, border: &amp;#39;1px solid black&amp;#39;, height: &amp;#39;600px&amp;#39; }}&amp;gt;
 { props.children }
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since I don&amp;rsquo;t need any of those CSS properties to change at any stage, I could just convert it to pure HTML/CSS right? Well no:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2023-01-09-at-4.26.54-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The newbie trap I&amp;rsquo;ve fallen for here is that although that &lt;code&gt;&amp;lt;div style= tag&lt;/code&gt; looks like HTML, it&amp;rsquo;s actually not. It&amp;rsquo;s not a template that will be filled out in the build step, it&amp;rsquo;s React code that will be used to mutate the virtual DOM.&lt;/p&gt;</description></item><item><title>De-structuring objects in JS</title><link>https://devendevour.iankulin.com/de-structuring-objects-in-js/</link><pubDate>Fri, 20 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/de-structuring-objects-in-js/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_robot_repair_shop_wth_several_humanoid_robots_in_state_o_c5ef7630-fb2a-4dec-acd5-dfb9b9f478ac.jpg" alt="robot repair shop wth several humanoid robots in state of disrepair, cinematic lighting, dust in the air - midjourney" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve worked through my first React tutorial app, and obviously that&amp;rsquo;s a lot - I&amp;rsquo;m struct by how messy mixing HTML, JS and React is.&lt;/p&gt;
&lt;p&gt;One language feature that&amp;rsquo;s being used quite a bit, and that is apparently a JS ability I&amp;rsquo;d never seen is &amp;lsquo;destructuring&amp;rsquo; object properties. It&amp;rsquo;s very cool and obviously useful. It&amp;rsquo;s a way of extracting just the properties you need from an object and then using them without accesing them via the object. An example will make it clearer.&lt;/p&gt;</description></item><item><title>Digital Color Meter</title><link>https://devendevour.iankulin.com/digital-color-meter/</link><pubDate>Wed, 18 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/digital-color-meter/</guid><description>&lt;p&gt;For the Calculator project, I needed to know the exact RGB values for the colours on the iOS calculator buttons so I could reproduce them. Assuming a tool for reading colours from the screen exisited, I googled it, and was surprised to find this exact tool is already installed by default on MacOS.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s called Digital Color Meter and just shows the RGB values for anything on the screen under the cursor.&lt;/p&gt;</description></item><item><title>Calculator</title><link>https://devendevour.iankulin.com/calculator-2/</link><pubDate>Mon, 16 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/calculator-2/</guid><description>&lt;p&gt;I&amp;rsquo;ve been doing a bit of driving during the holidays, which means a lot of podcast listening. An episode of &lt;a href="https://topenddevs.com/podcasts/javascript-jabber/episodes/splatty-doo-and-other-javascript-features-you-should-avoid-jsj-543" target="_blank" rel="noopener"&gt;JavaScript Jabber about JS features you should never use&lt;/a&gt; sparked my interest in &lt;code&gt;[eval()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval)&lt;/code&gt;. &lt;code&gt;eval()&lt;/code&gt; takes whatever you pass it in a string and executes it in the JS engine. This is a crazy concept if you&amp;rsquo;ve come from complied languages, and has obvious security implications. As with dynamic typing, I&amp;rsquo;m trying to force myself out of my comfort zone to embrace JS&amp;rsquo;s unique talents so I was keen to try &lt;code&gt;eval()&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>CWD - 185 - Problem solving</title><link>https://devendevour.iankulin.com/cwd-185-problem-solving/</link><pubDate>Sat, 14 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/cwd-185-problem-solving/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_c-3po_from_star_wars_on_tatooine_playing_tic-tac-toe_on__66c1149e-de97-45d4-9863-18181aa54cf7.jpg" alt="C-3PO from Star Wars on Tatooine, playing Tic-tac-toe on the side of a crashed spaceship - MidJourney" class="img-responsive"&gt; &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/* 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;Question 1: Clean the room function: given an input of [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20], 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;make a function that organizes these into individual array that is ordered. For example 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;answer(ArrayFromAbove) should return: [[1,1,1,1],[2,2,2], 4,5,10,[20,20], 391, 392,591]. 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ctrFunction1&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;inputArray&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// copy the array since we&amp;#39;re mutating it
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;array&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [...&lt;span style="color:#a6e22e"&gt;inputArray&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;array&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;sort&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;numberObject&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;number&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;array&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;numberObject&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;number&lt;/span&gt;] &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;undefined&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// this property does not exist, so add it
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;numberObject&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;number&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;numberObject&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;number&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;push&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;number&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// object now contains arrays for each number, but the ones with a
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// single element need degloved
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;property&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;numberObject&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;numberObject&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;property&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;numberObject&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;property&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;numberObject&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;property&lt;/span&gt;][&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// now turn back to array 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Object.&lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;numberObject&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;array1&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;591&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;392&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;391&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;2&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;transformedArray1&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ctrFunction1&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;array1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;transformedArray1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// [1, 1, 1, 1], [2, 2, &amp;#39;2&amp;#39;], 4, 5, 10, [20, 20], 391, 392, 591]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="line-10"&gt;Line 10&lt;/h4&gt; &lt;p&gt;When I&amp;rsquo;m looking at a function, I&amp;rsquo;d prefer not to also have to hold global state in my head - so I&amp;rsquo;m all for functional programming as far as that goes. I&amp;rsquo;m less concerned about side effects, so I wouldn&amp;rsquo;t always bother to copy a parameter like this, but the argument is stronger for an array than an object since in other languages an array might be a value type.&lt;/p&gt;</description></item><item><title>Types of Concern</title><link>https://devendevour.iankulin.com/types-of-concern/</link><pubDate>Fri, 13 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/types-of-concern/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_medieval_manuscript_drawing_of_a_peasant_holding_a_macbo_534a8a13-44b2-44cc-8916-5e246609e39d.jpg" alt="medieval manuscript drawing of a peasant holding a MacBook - midjourney" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I am still struggling with the dynamic typing of JS. I guess the benefit of such an approach is needing less characters - which makes sense in a scripting language like bash, but in real programming it opens us up to a whole class of avoidable errors.&lt;/p&gt;
&lt;p&gt;To program defensively in JS would mean loading the start of function with a series of type checks. I don&amp;rsquo;t see much of that in other people&amp;rsquo;s code, so I assume we just, don&amp;rsquo;t?&lt;/p&gt;</description></item><item><title>Lost in Translation</title><link>https://devendevour.iankulin.com/lost-in-translation/</link><pubDate>Wed, 11 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/lost-in-translation/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_two_female_robots_having_a_confusing_conversation_2d32f035-3a1e-4e9f-a335-f7c395190925.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re in a pretty good place now (compared to a few years ago) in terms of being able to rely on JavaScript behaving the same on different platforms. There&amp;rsquo;s still some differences (mostly in when things are implemented) but overall, not to bad once you decide to no longer support Internet Explorer.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript" target="_blank" rel="noopener"&gt;In times past, it was a lot more painful&lt;/a&gt; . A few of approaches to deal with this arose. One is to let a library, such as &lt;a href="https://jquery.com/" target="_blank" rel="noopener"&gt;jQuery&lt;/a&gt; or a &lt;a href="https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills" target="_blank" rel="noopener"&gt;polyfill&lt;/a&gt; deal with it, and the other is use a translation utility such as Babel to down convert (transpile) your modern JavaScript to something that will run in more browsers.&lt;/p&gt;</description></item><item><title>Functions in JavaScript</title><link>https://devendevour.iankulin.com/functions-in-javascript/</link><pubDate>Mon, 09 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/functions-in-javascript/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_factory_with_robot_workers_on_an_assembly_line_making_bo_dadc6d24-8873-48f2-97aa-df5508e6e625-1.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;As with other languages, functions are a little lumps of code with their own scope. They can optionally take some arguments, and optionally return a value.&lt;/p&gt;
&lt;p&gt;In JavaScript they often have names, can be passed around as types and have a condensed form suitable for functional programming.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;function addNums(a, b) {
 return a+b;
}

console.log(addNums(3,4)) // 7
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="scope"&gt;Scope&lt;/h4&gt; &lt;p&gt;Arguments are passed in by value so they have local scope only in the function body.&lt;/p&gt;</description></item><item><title>Classes in JavaScript</title><link>https://devendevour.iankulin.com/classes-in-javascript/</link><pubDate>Sat, 07 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/classes-in-javascript/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_futuristic_machine_making_copies_of_people_f1076d37-add2-4592-952b-ac8ac30c7a5c.jpg" alt="futuristic machine making copies of people - midjourney" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;First lesson with classes today. First of all I was pleased to see they exists since we&amp;rsquo;ve just been plucking objects out of thing air like:&lt;code&gt;}&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const userIan = {name: &amp;#39;Ian&amp;#39;, language: &amp;#39;Indonesian&amp;#39;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;but with classes we can declare a class and instantiate an object of it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class User {
 constructor(name, language) {
 this.name = name;
 this.language = language;
 }
}

const ian = new User(&amp;#39;Ian&amp;#39;, &amp;#39;Indonesian&amp;#39;);
console.log(ian.name);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&amp;rsquo;s (at least) single inheritance:&lt;/p&gt;</description></item><item><title>Things I love about Swift after a week of JavaScript</title><link>https://devendevour.iankulin.com/things-i-love-about-swift-after-a-week-of-javascript/</link><pubDate>Fri, 06 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/things-i-love-about-swift-after-a-week-of-javascript/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/swift-logo-punching-the-javascript-logo.jpg" alt="Swift logo punching the JavaScript logo - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;So, a week into JavaScript, what am I missing? The techie in me wants to say things like Automatic Reference Counting, but actually, at my junior level, I don&amp;rsquo;t run into memory management issues on the day-to-day, so what really do I miss?&lt;/p&gt;
&lt;h4 id="determinism"&gt;Determinism&lt;/h4&gt; &lt;p&gt;When I build an iOS app, it&amp;rsquo;s frozen in time. The functions inside are always going to stay the same. There might be a future version of iOS that won&amp;rsquo;t run it, but as long as it runs, any pure functions inside it will return the same value. The process of compiling it locks that in. Likewise, any libraries that are complied with it.&lt;/p&gt;</description></item><item><title>CodePen.io</title><link>https://devendevour.iankulin.com/codepen-io/</link><pubDate>Thu, 05 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/codepen-io/</guid><description>&lt;p&gt;I quite often leave a link to a GitHub repo to share my source in these posts, and on a few recent ones, a link to a live version of a page on my github.io. In a recent installment of &lt;a href="https://www.udemy.com/course/the-complete-web-developer-zero-to-mastery/" target="_blank" rel="noopener"&gt;CWD&lt;/a&gt; , Andrei shared some previous students&amp;rsquo; solutions, and some were hosted on CodePen.io which I hadn&amp;rsquo;t seen before.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-12-28-at-10.20.02-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a cute concept, you can enter HTML, CSS &amp;amp; JS and see a live view of the page below. It looks super extensible - there&amp;rsquo;s mentions of SCSS, Typescript and preprocessors for JS in the settings.&lt;/p&gt;</description></item><item><title>Second Guessing</title><link>https://devendevour.iankulin.com/second-guessing/</link><pubDate>Thu, 05 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/second-guessing/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_young_women_identical_twins_enjoying_the_beach_unreal_en_89773710-ae61-42d7-bede-d74ef60dafd3.jpg" alt="young women identical twins enjoying the beach unreal engine - midjourney" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;In the last post, I was pleased with myself for accidentally anticipating an improvement to a tutorial project which turned out to be the next task, today I&amp;rsquo;m pleased with myself for discussing the pros and cons of &lt;code&gt;onclick=&lt;/code&gt; vs &lt;code&gt;addEventListener()&lt;/code&gt; then having that same discussion turn up in the next tutorial. I take it as an indication that I am correctly immersing myself in the subject.&lt;/p&gt;</description></item><item><title>Step Ahead</title><link>https://devendevour.iankulin.com/one-step/</link><pubDate>Wed, 04 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/one-step/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_giant_girl_leaping_for_joy_in_the_forest_childrens_book__eadb2294-124d-46e0-a0ca-f94a1a9305ea.jpg" alt="giant girl leaping for joy in the forest children&amp;rsquo;s book - midjourney" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I was a bit pleased with myself when I started the next content element in the Complete Web Developer course to find that one and a half of the extensions I&amp;rsquo;d made to the tutorial app for my own fun were specified as the next task.&lt;/p&gt;
&lt;p&gt;In my previous post, I&amp;rsquo;d talked about using a class to denote if an item was completed, and using a style to indicate this by crossing it out. What I haven&amp;rsquo;t discussed was that I&amp;rsquo;d captured right click events on the list items to make this delete them. I wasn&amp;rsquo;t entirely happy with that for a couple of reasons:&lt;/p&gt;</description></item><item><title>Web Reference</title><link>https://devendevour.iankulin.com/web-reference/</link><pubDate>Tue, 03 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/web-reference/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_1950s_man_and_woman_sitting_back_to_back_both_reading_la_2e94f56b-03c4-4b85-90ef-409f825e77ed.jpg" alt="1950&amp;rsquo;s man and woman sitting back to back both reading large books outside by swimming pool - midjourney" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/pucker_1950s_man_and_woman_sitting_back_to_back_both_reading_la_f12252b9-78dd-4ad1-bd76-bd85654cdaa1.jpg" alt="1950&amp;rsquo;s man and woman sitting back to back both reading large books outside by swimming pool - midjourney" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;There is no shortage of places to reference material to help when developing web apps, including a gazillion tutorials and blog posts (like mine) of various quality, and more importantly - based on the state of play at the time they were written, which could be any time in the last twenty years. I keep bumping up against this - great, clear explanations addressing whatever I was googling, but which turn out to use out of date bits.&lt;/p&gt;</description></item><item><title>Document Object Model - ToDo</title><link>https://devendevour.iankulin.com/document-object-model-todo/</link><pubDate>Mon, 02 Jan 2023 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/document-object-model-todo/</guid><description>&lt;p&gt;I&amp;rsquo;m up to Section 12 of the Complete Web Developer course &amp;ldquo;DOM Manipulation&amp;rdquo; and it feels like we&amp;rsquo;re finally at the stage of pulling everything (HTML, CSS &amp;amp; JavaScript) together to make minimal web apps. Since the course is light on building challenges, I&amp;rsquo;ve set myself one - to make a simple todo list (the classic step up from &amp;ldquo;hello world&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;The Document Object Model is an entity representing the HTML with attached CSS for a page. The magic is that we can access this in JavaScript, and therefore change it, including hooking into events on it - such as a user pressing a button.&lt;/p&gt;</description></item><item><title>Are you okay JavaScript arrays?</title><link>https://devendevour.iankulin.com/are-you-okay-javascript-arrays/</link><pubDate>Sat, 31 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/are-you-okay-javascript-arrays/</guid><description>&lt;p&gt;As a visitor from sensible type-safe land, this makes me uncomfortable:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-12-23-at-8.52.06-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;As I keep learning, I&amp;rsquo;m interested to find out if JavaScript objects turn out to just be arrays. To get from here to there, you&amp;rsquo;d just need to be able to use some sort of self[2] notation to access properties from inside the functions.&lt;/p&gt;</description></item><item><title>Curse of Backwards Compatibility</title><link>https://devendevour.iankulin.com/curse-of-backwards-compatibility/</link><pubDate>Thu, 29 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/curse-of-backwards-compatibility/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/young-woman-looking-back-over-her-shoulder-impressionist-painting.jpg" alt="young woman looking back over her shoulder, Impressionist painting - Stable diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I was listening to a JavaScript podcast today (&lt;a href="https://www.youtube.com/watch?v=O0fvMJcca3A" target="_blank" rel="noopener"&gt;JavaScript Jabber&lt;/a&gt; ) and in one of the discussions a point was made about how HTML, CSS and JavaScript have all had to maintain considerable legacy behaviors that compile-able languages do not have to. For instance, when Swift underwent some substantial changes from Swift 2 to Swift 3 - some code broke for developers and needed reworking because things had changed or been removed. Nothing broke for users - they could either still use their previously compiled applications, or they were delivered new ones from the app store.&lt;/p&gt;</description></item><item><title>Running Javascript in VS Code</title><link>https://devendevour.iankulin.com/running-javascript-in-vs-code/</link><pubDate>Tue, 27 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/running-javascript-in-vs-code/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-12-21-at-11.08.17-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer" target="_blank" rel="noopener"&gt;Live Server&lt;/a&gt; plugin to see HTML &amp;amp; CSS updated as I edit, and that will also be useful when I start using Javascript for web development, but as you can see above, I&amp;rsquo;m not quite up to that. It seemed there should be a way to run JS in VS Code, and it turns out it&amp;rsquo;s easy.&lt;/p&gt;
&lt;p&gt;You just need something installed that can run Javascript. Node.js is the obvious choice, and you&amp;rsquo;re going to need it later in your development journey. Just i&lt;a href="https://nodejs.org/en/download/" target="_blank" rel="noopener"&gt;nstall Node.js&lt;/a&gt; then the first time you try to run some JS in VS code, it will ask you what to use, select Node and you&amp;rsquo;re in business.&lt;/p&gt;</description></item><item><title>99 CSS Layout Feedback</title><link>https://devendevour.iankulin.com/99-css-layout-feedback/</link><pubDate>Sun, 25 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/99-css-layout-feedback/</guid><description>&lt;p&gt;I&amp;rsquo;ve been in the swing with the &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;#100DaysOfSwiftUI&lt;/a&gt; course of having frequent assignments to test my understanding of the course content up to that point, then watching the feedback video and reflecting on it here. So far, in the &lt;a href="https://www.udemy.com/course/the-complete-web-developer-zero-to-mastery/" target="_blank" rel="noopener"&gt;Complete Web Developer&lt;/a&gt; I&amp;rsquo;ve only had this single CSS assignment, so I was excited to see how I got on.&lt;/p&gt;
&lt;p&gt;I was a bit chuffed that one of Andrei&amp;rsquo;s first actions was to edit the html to make it more semantic - I&amp;rsquo;d used &lt;header&gt; for the top bit, he used &lt;nav&gt; which is probably better, and then I could have recycled &lt;header&gt; for the cover. Although in general, there was a lot of use of classes, where I had just used selectors carefully. I guess my thinking here was that the html should be free of information about how to display it - and we break that if we say add &lt;code&gt;class=&amp;quot;sticky&amp;quot;&lt;/code&gt; to the nav bar. The argument could be made the other way though - with my system I&amp;rsquo;m building dependency on a particular page structure into the CSS if I use a selector to pick out the last child in a list to apply a style to it.&lt;/p&gt;</description></item><item><title>99 CSS Layout challenge</title><link>https://devendevour.iankulin.com/99-css-layout-challenge/</link><pubDate>Fri, 23 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/99-css-layout-challenge/</guid><description>&lt;p&gt;In the &lt;a href="https://zerotomastery.io/" target="_blank" rel="noopener"&gt;Zero To Mastery&lt;/a&gt; &lt;a href="https://www.udemy.com/course/the-complete-web-developer-zero-to-mastery/" target="_blank" rel="noopener"&gt;Complete Web Developer&lt;/a&gt; course, I&amp;rsquo;m up to the first practical challenge - to use CSS to layout a reasonably standard looking web page using flex-box and grid to make it responsive.&lt;/p&gt;
&lt;p&gt;Frustratingly, both for writing this, and while I was trying to build the page, I&amp;rsquo;m unable to screenshot the example of the page I was supposed to be building, and instead had to keep opening the video and seeking the two second flash of the completed project, and eventually being reduced to photographing my laptop screen like a boomer relative sending me a meme:&lt;/p&gt;</description></item><item><title>HTML 002 - Tags for structure</title><link>https://devendevour.iankulin.com/html-002-tags-for-structure/</link><pubDate>Wed, 21 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/html-002-tags-for-structure/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/young-woman-at-a-computer-writing-html-impressionistic.jpg" alt="Young woman at a computer writing html, Impressionistic - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I briefly mentioned &lt;a href="https://devendevour.iankulin.com/html-001/"&gt;earlier&lt;/a&gt; that our HTML tags should flag WHAT this part of the document is rather than how to display it (we&amp;rsquo;ll look at how to use CSS for making the content look how we want later). This idea is called semantic HTML. This post will look at some of the tags (often called &lt;a href="https://www.w3schools.com/html/html5_semantic_elements.asp" target="_blank" rel="noopener"&gt;semantic tags&lt;/a&gt; ) we use to convey knowledge of what part of each document an element is.&lt;/p&gt;</description></item><item><title>HTML 001</title><link>https://devendevour.iankulin.com/html-001/</link><pubDate>Fri, 16 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/html-001/</guid><description>&lt;p&gt;A HTML file is a text file that can be displayed in a web browser. It is &lt;em&gt;marked up&lt;/em&gt; in the sense that &lt;em&gt;tags&lt;/em&gt; are applied to the text to signify the purpose of that text in the structure of the document. For example:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;h1&amp;gt;Greetings&amp;lt;/h1&amp;gt;
Hello Earthlings
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; tag tells the browser that &lt;code&gt;Greetings&lt;/code&gt; is a heading. The heading tag is &lt;em&gt;paired&lt;/em&gt;. There&amp;rsquo;s an opening tag &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; and closing tag &lt;code&gt;&amp;lt;/h1&amp;gt;&lt;/code&gt; that let the browser know where the heading starts and ends. Most tags are paired, but there are some &lt;em&gt;unpaired&lt;/em&gt; tags such as &lt;br&gt; which inserts a line break.&lt;/p&gt;</description></item><item><title>CSS for Beginners</title><link>https://devendevour.iankulin.com/css-for-beginners/</link><pubDate>Thu, 15 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/css-for-beginners/</guid><description>&lt;p&gt;I mentioned a couple of days ago that the ZTM webdev course was skipping forwards too quick and that it would need to be supplemented. For CSS, I think the supplement for me is going to be this &lt;a href="https://www.youtube.com/playlist?list=PL0Zuz27SZ-6Mx9fd9elt80G1bPcySmWit" target="_blank" rel="noopener"&gt;series&lt;/a&gt; from Dave Gray.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/0W6qz0-aDaM?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;</description></item><item><title>Who is Emmet?</title><link>https://devendevour.iankulin.com/who-is-emmet/</link><pubDate>Wed, 14 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/who-is-emmet/</guid><description>&lt;p&gt;&lt;a href="https://www.piqsels.com/en/public-domain-photo-ircsa" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/css-hacks.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;I knew there was some magical way of entering all the the &lt;HTML&gt; boilerplate in Visual Studio Code as I&amp;rsquo;d seen it happen in several videos, and assumed is was some sort of macro expansion thing in the editor. Fast forward a few blog post readings and youtube viewings and I keep seeing tangential references to someone called Emmet. Turns out they&amp;rsquo;re the same thing, and it&amp;rsquo;s pretty cool.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not a new idea to have functionality in code editors to insert snippets of code. &lt;a href="https://docs.emmet.io/" target="_blank" rel="noopener"&gt;Emmet&lt;/a&gt; goes a bit further than that - and like many tools made by programmers for programmers it goes way to technical to the point where you need to memorise ridiculous amounts of combos to to some awesome stuff (I&amp;rsquo;m looking at you whoever made it possible to use vi commands in VS Code). Nevertheless, Emmet is extremely handy even at my n00b level.&lt;/p&gt;</description></item><item><title>ZTM - Complete Web Developer</title><link>https://devendevour.iankulin.com/ztm-complete-web-developer/</link><pubDate>Tue, 13 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ztm-complete-web-developer/</guid><description>&lt;p&gt;&lt;a href="https://zerotomastery.io/courses/coding-bootcamp/" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-12-11-at-8.31.15-pm.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;I started my first Udemy a few days ago. I was watching one of those &amp;ldquo;&lt;a href="https://www.youtube.com/watch?v=cYNVVspXUdA" target="_blank" rel="noopener"&gt;How I&amp;rsquo;d learn to code if I started over&lt;/a&gt; &amp;rdquo; YouTubes, mainly because I&amp;rsquo;d like to know enough JavaScript to write little REST API&amp;rsquo;s on Node.js, but also because I&amp;rsquo;m starting to think web development makes more sense for a couple of the applications I&amp;rsquo;ve got on my (ever growing) list of app ideas.&lt;/p&gt;</description></item><item><title>Visual Studio Code</title><link>https://devendevour.iankulin.com/visual-studio-code-2/</link><pubDate>Mon, 12 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/visual-studio-code-2/</guid><description>&lt;p&gt;I&amp;rsquo;ve gone over to the dark side a little. As I think about the sort of apps I want to make, I realise I am going to need to be able to do back-end web development. My apps are going to need a secure REST api to a database. I guess that means node.js. I&amp;rsquo;m also conscious that my ticket app needs to run on android, and a short cut around all of that might be to make the whole thing a web app from the start, but with the premium experience on iOS.&lt;/p&gt;</description></item><item><title>ChatGPT's code writing</title><link>https://devendevour.iankulin.com/chatgpts-code-writing/</link><pubDate>Sun, 11 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/chatgpts-code-writing/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/22.jpg" alt="room full of monkeys typing at computers - stable diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;This week, the internet has been all about &lt;a href="https://chat.openai.com/chat" target="_blank" rel="noopener"&gt;ChatGPT&lt;/a&gt; , the rather remarkable natural language AI with a very large model. If you&amp;rsquo;re a twitter user, you were probably amazed, but now eventually tired of seeing examples of it&amp;rsquo;s output. I&amp;rsquo;ll add to that with an example of a SwifUI CoreData based todo app it wrote for me from a single sentence prompt below. Rather than look at other people&amp;rsquo;s examples you should definitely go and play with it yourself - it is very impressive. Along with the image based AI&amp;rsquo;s it&amp;rsquo;s made 2022 into a historical year for AI.&lt;/p&gt;</description></item><item><title>Sharing is caring</title><link>https://devendevour.iankulin.com/sharing-is-caring/</link><pubDate>Sat, 10 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/sharing-is-caring/</guid><description>&lt;p&gt;Continuing on with the demo project from yesterday, in which we used the ImageRenderer class to turn a view into an image, today we want to let the user share it somehow.&lt;/p&gt;
&lt;p&gt;Typically, apps have a button using the square.and.arrow.up SF Symbol to share something from the current screen. It&amp;rsquo;s probably not an accident that it&amp;rsquo;s literally the first symbol in the app.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-12-05-at-9.23.33-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Pressing it generally opens the &amp;ldquo;share sheet&amp;rdquo; which has options for opening whatever is being shared in another app, printing it, saving it to photos, or whatever.&lt;/p&gt;</description></item><item><title>Web Development Links</title><link>https://devendevour.iankulin.com/web-development-links/</link><pubDate>Sat, 10 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/web-development-links/</guid><description>&lt;p&gt;Sites recommended by the Complete Web Developer course I&amp;rsquo;m doing on Udemy.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.w3schools.com/" target="_blank" rel="noopener"&gt;https://www.w3schools.com/&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="https://css-tricks.com/" target="_blank" rel="noopener"&gt;https://css-tricks.com/&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="https://paletton.com/" target="_blank" rel="noopener"&gt;https://paletton.com/&lt;/a&gt; - web colour picker&lt;/p&gt;
&lt;p&gt;&lt;a href="https://animate.style" target="_blank" rel="noopener"&gt;https://animate.style&lt;/a&gt; / - css animations&lt;/p&gt;</description></item><item><title>Clean code</title><link>https://devendevour.iankulin.com/clean-code/</link><pubDate>Fri, 09 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/clean-code/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/young-woman-cleaning-a-computer-painting.jpg" alt="young woman cleaning a computer, painting - stable diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been listening to the &lt;a href="https://www.youtube.com/watch?v=YVrHPCZnC50" target="_blank" rel="noopener"&gt;latest episode of the Empower Apps&lt;/a&gt; podcast, this one with &lt;a href="https://twitter.com/Jilsco9" target="_blank" rel="noopener"&gt;Jill Scott&lt;/a&gt; talking about &amp;ldquo;Humane&amp;rdquo; development - in the sense of being humane to whoever (probably you) is going to be reading this code in the future. It helped me clarify my thoughts about a couple of things.&lt;/p&gt;
&lt;p&gt;None of these ideas are particularly new or groundbreaking, and although I think of them as my personal style, they are very common, and in Swift could be regarded as part of the culture. Some of these concepts support each other, some represent a trade off between two opposing ideas that require us to make a choice.&lt;/p&gt;</description></item><item><title>ImageRenderer()</title><link>https://devendevour.iankulin.com/imagerenderer/</link><pubDate>Thu, 08 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/imagerenderer/</guid><description>&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/swiftui/imagerenderer" target="_blank" rel="noopener"&gt;ImageRenderer&lt;/a&gt; () is a SwiftUI class that creates an image from a view. You just initialize it with the view, then extract a cgImage (Core Graphics) or uiImage that can be cast to a SwiftUI Image.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll need a view to work with, so here it is; a crude version of my behaviour ticket.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TicketView&lt;/span&gt;: View {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; body: some View {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ZStack {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Color(.cyan)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .frame(width: &lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;, height: &lt;span style="color:#ae81ff"&gt;350&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&lt;span style="color:#e6db74"&gt;&amp;#34;Fred Bloggs&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .font(.largeTitle)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&lt;span style="color:#e6db74"&gt;&amp;#34;Putting rubbish in the bin&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Image(systemName: &lt;span style="color:#e6db74"&gt;&amp;#34;trash&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .foregroundColor(.purple)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&lt;span style="color:#e6db74"&gt;&amp;#34;Green Faction&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;\(&lt;/span&gt;Date&lt;span style="color:#e6db74"&gt;()&lt;/span&gt;.formatted&lt;span style="color:#e6db74"&gt;())&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here it is, with a couple of buttons underneath:&lt;/p&gt;</description></item><item><title>SwiftUI provides</title><link>https://devendevour.iankulin.com/swiftui-provides/</link><pubDate>Wed, 07 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/swiftui-provides/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_3476.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A few hours after I speculated about pausing work on the tickets app because outputting the tickets was too far out of my expertise, a helpful instance of the &lt;a href="https://en.wikipedia.org/wiki/Frequency_illusion" target="_blank" rel="noopener"&gt;Baader–Meinhof phenomenon&lt;/a&gt; threw up some help in the form of this tweet from &lt;a href="https://twitter.com/flowritescode" target="_blank" rel="noopener"&gt;@FloWritesCode&lt;/a&gt; . It turns out this was an addition in iOS16 announced at WWDC that makes this straightforward.&lt;/p&gt;
&lt;p&gt;As soon as I googled around about it I also found good solutions that wrapped the old code to provide similar functionality. So that&amp;rsquo;s a lesson for me about not assuming something&amp;rsquo;s hard before I&amp;rsquo;ve spent some time investigating it. I took that lesson and applied it to rendering to a PDF, and of course, @twostraws &lt;a href="https://www.hackingwithswift.com/quick-start/swiftui/how-to-render-a-swiftui-view-to-a-pdf" target="_blank" rel="noopener"&gt;has a code example&lt;/a&gt; for that from three days ago!&lt;/p&gt;</description></item><item><title>Committed</title><link>https://devendevour.iankulin.com/committed/</link><pubDate>Tue, 06 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/committed/</guid><description>&lt;p&gt;I quite like logging into GitHub and seeing my commit history as the graph with the green dots. Once I get up to a year it would be a great thing to have on a T-Shirt.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-12-03-at-7.36.29-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d expect to be seeing the busy weekends, but Tuesday nights seem to be oddly productive. It could just be a start of the week energy thing - I have some other community obligations on a couple of Monday nights a month.&lt;/p&gt;</description></item><item><title>Ticket to ride</title><link>https://devendevour.iankulin.com/ticket-to-ride/</link><pubDate>Mon, 05 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ticket-to-ride/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/superman-crashing-into-a-train-cartoon-2.jpg" alt="superman crashing into a train, cartoon - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A &lt;a href="https://devendevour.iankulin.com/project-based-learning/"&gt;couple of days ago&lt;/a&gt; I was lauding the learning benefits of writing your own projects over completing tutorial projects - since your own projects push your boundaries further. Of course, its also the case that the project requirements might so completely exceed your current ability that it grinds to a halt. That&amp;rsquo;s the case with my &lt;a href="https://devendevour.iankulin.com/tickets-on-myself/"&gt;behaviour ticket app&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;The part of the app for collecting the data is pretty much done and how I imagined it, but the output needs to be pretty tickets that can be printed on paper. I managed to write the ticket data to a CSV file and export that to the files app with a .fileExporter, but really what I wanted is to have one of those share screens where you can chose to AirDrop, Print etc, and for the tickets to have been rendered to a PDF or series of images to be shared. That will have to wait. I&amp;rsquo;m just up to a bit in the #100Days about writing images so I&amp;rsquo;ll push on with that for a bit and come back to my app.&lt;/p&gt;</description></item><item><title>Pi Server</title><link>https://devendevour.iankulin.com/pi-server/</link><pubDate>Sun, 04 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/pi-server/</guid><description>&lt;p&gt;I have a a couple of Raspberry Pi&amp;rsquo;s on my home network. One is a radio interface on the &lt;a href="https://www.allstarlink.org/" target="_blank" rel="noopener"&gt;AllStar network&lt;/a&gt; , and the other is just a toy server - I can&amp;rsquo;t actually recall why I bought it. Both of them are Model 3B&amp;rsquo;s - I&amp;rsquo;d love a 4, but they are scarce and expensive.&lt;/p&gt;
&lt;p&gt;This doesn&amp;rsquo;t have much to do with Swift, although it&amp;rsquo;s possible to run &lt;a href="https://lickability.com/blog/swift-on-raspberry-pi/" target="_blank" rel="noopener"&gt;Swift on a Pi&lt;/a&gt; , or even &lt;a href="https://medium.com/@jhheider/installing-vapor-and-swift-on-the-raspberry-pi-45a6c7baef35" target="_blank" rel="noopener"&gt;Vapor&lt;/a&gt; . Mine is set up as a generic web server that I use as the back end for my tiny projects. It runs &lt;a href="https://nodejs.org/en/about/" target="_blank" rel="noopener"&gt;Node.js&lt;/a&gt; , &lt;a href="https://www.apache.org/" target="_blank" rel="noopener"&gt;apache&lt;/a&gt; and &lt;a href="https://www.lighttpd.net/" target="_blank" rel="noopener"&gt;lighttpd&lt;/a&gt; webservers, &lt;a href="https://www.php.net/" target="_blank" rel="noopener"&gt;PHP&lt;/a&gt; , &lt;a href="https://www.mysql.com/" target="_blank" rel="noopener"&gt;MySQL&lt;/a&gt; , &lt;a href="https://www.sqlite.org/index.html" target="_blank" rel="noopener"&gt;SQLite&lt;/a&gt; , and, when I get to that stage of my programmming, &lt;a href="https://pimylifeup.com/raspberry-pi-postgresql/" target="_blank" rel="noopener"&gt;Postgres&lt;/a&gt; . I could do all that on my MacBook, but it&amp;rsquo;s somehow more fun on the Pi.&lt;/p&gt;</description></item><item><title>Towards MVVM</title><link>https://devendevour.iankulin.com/towards-mvvm/</link><pubDate>Sat, 03 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/towards-mvvm/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/young-woman-swimming-in-spaghetti-by-hokusai-.jpg" alt="young woman swimming in spaghetti, by Hokusai - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;On one of the more mediocre &lt;a href="https://firesideswift.fireside.fm/96" target="_blank" rel="noopener"&gt;episodes of Fireside Swift&lt;/a&gt; , McSwiftface and Zach talk about the &lt;a href="https://en.wikipedia.org/wiki/SOLID" target="_blank" rel="noopener"&gt;SOLID principles&lt;/a&gt; of class design, although I don&amp;rsquo;t hold the principles as the article of religious fervour that many interviewers apparently do, they are a useful touchstone for considering class quality. OOP had been in swing (in a commercial way) for a few years by then - I was writing in Delphi and C++. The spaghetti code era was a long way behind us and the idea of separation of responsibilities was well established.&lt;/p&gt;</description></item><item><title>Deep Linking</title><link>https://devendevour.iankulin.com/deep-linking/</link><pubDate>Fri, 02 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/deep-linking/</guid><description>&lt;p&gt;I was listening to an &lt;a href="https://firesideswift.fireside.fm/100" target="_blank" rel="noopener"&gt;old episode of Fireside Swift&lt;/a&gt; today discussing NFC tags. I have a bundle of this tags in a drawer here somewhere - I thought it would be cool to tap one as I came home to turn off the CCTV and some other home automation things. But it turns out my phone (an SE2) has the capability for this, but only inside an app - not just from anywhere, whereas the proper phones can just tap anytime, and if the NFC payload is set up correctly, follow a URL, including by &amp;ldquo;deep linking&amp;rdquo; into an app.&lt;/p&gt;</description></item><item><title>Project Based Learning</title><link>https://devendevour.iankulin.com/project-based-learning/</link><pubDate>Thu, 01 Dec 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/project-based-learning/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/young-woman-holding-a-phone-outside-near-a-lake-painting.jpg" alt="young woman holding a phone outside near a lake, painting - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A couple of times in conversations on &lt;a href="https://firesideswift.fireside.fm/" target="_blank" rel="noopener"&gt;Fireside Swift&lt;/a&gt; and &lt;a href="https://podcasts.apple.com/au/podcast/swift-over-coffee/id1435076502" target="_blank" rel="noopener"&gt;Swift Over Coffee&lt;/a&gt; the presenters have talked about the danger of just doing more and more tutorials to learn programming, and the benefit, in contrast, of building your own real app. Although I am very much still benefiting from the 100DaysOfSwiftUI I have been seeing some of the upside of working on a real app in the last day and a half.&lt;/p&gt;</description></item><item><title>Regex to split a string with two different characters</title><link>https://devendevour.iankulin.com/regex-to-split-a-string-with-two-different-characters/</link><pubDate>Wed, 30 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/regex-to-split-a-string-with-two-different-characters/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/young-woman-cutting-string-painting-by.jpg" alt="young woman cutting string, painting by - StableDiffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m working on the behaviour tickets app, and wanted a visually functional version to share with stakeholders this week to get some feedback. As usual in this situation, I&amp;rsquo;m pressed for time so feeling the pressure to take some liberties with code quality that I&amp;rsquo;ll come back and fix one day.&lt;/p&gt;
&lt;p&gt;In a salient lesson of why that&amp;rsquo;s usually a bad idea, I&amp;rsquo;ve ended up googling to try and understand regex instead of writing code.&lt;/p&gt;</description></item><item><title>Copying a file via SSH</title><link>https://devendevour.iankulin.com/copying-a-file-via-ssh/</link><pubDate>Tue, 29 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/copying-a-file-via-ssh/</guid><description>&lt;p&gt;I have a Raspberry Pi on my home network that I purchased for some project that I can&amp;rsquo;t actually recall. It gets used for all sorts of completely unnecessary things such as playing with node.js or a private git server. To add to the list of things that I do on pi that could be more efficiently done on my MacBook I wanted to host my sample JSON from yesterday on it.&lt;/p&gt;</description></item><item><title>Mock Data</title><link>https://devendevour.iankulin.com/mock-data/</link><pubDate>Mon, 28 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/mock-data/</guid><description>&lt;p&gt;One of the things we need during app development is some data to play with. It would be unethical for me to use real student data to test my app, even if I wasn&amp;rsquo;t sharing screenshots of the development here, so I&amp;rsquo;ll need to build some mock data. The prospect of making 400 rows of data manually does not sound like a good use of time, so I started to think about generating it in Excel. I&amp;rsquo;d used an online &amp;ldquo;random address generator&amp;rdquo; for an earlier project, so I was contemplating pasting that sort of data into Excel workbooks and randomly selecting from it.&lt;/p&gt;</description></item><item><title>Tickets on Myself</title><link>https://devendevour.iankulin.com/tickets-on-myself/</link><pubDate>Sun, 27 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/tickets-on-myself/</guid><description>&lt;p&gt;Way back on Day 47 I wrote a little habit tracking app. It was the challenge at the end of a JSON tutorial, so the persistence is done by writing the JSON to UserDefaults as a string. Basic as it is, it&amp;rsquo;s installed on my phone and I check it a couple of times a day, and haven&amp;rsquo;t missed a day of coding, or the weekly bin day since. It&amp;rsquo;s strangely motivating.&lt;/p&gt;</description></item><item><title>FriendFace 61 Feedback</title><link>https://devendevour.iankulin.com/friendface-61-feedback/</link><pubDate>Sat, 26 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/friendface-61-feedback/</guid><description>&lt;p&gt;As usual after a challenge, I compare my efforts to Paul&amp;rsquo;s model solution. Just to quickly recap the app, it sucks up some data (Users who have multiple friends) and displays it. The change in this challenge was to convert it to add that data to a Core Data store so that if a future network error prevented accessing new data, it could still display the old.&lt;/p&gt;
&lt;h4 id="merge-policy"&gt;Merge Policy&lt;/h4&gt; &lt;p&gt;The first difference is that Paul adds a merge policy. A Merge policy tells Core how to deal with any constraints defined in the data model. In this app, I&amp;rsquo;d defined the CachedUser.id as a constraint. The purpose of this is that under normal conditions the app would be picking up mostly the same data each time it started up. We don&amp;rsquo;t want scabs of duplicate data, so constraining users based on their unique id is smart.&lt;/p&gt;</description></item><item><title>61 Done</title><link>https://devendevour.iankulin.com/61-done/</link><pubDate>Fri, 25 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/61-done/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/green_tick.svg_.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I think I&amp;rsquo;ve finally completed the minimum work for Day 61 of &lt;a href="https://www.hackingwithswift.com/100/swiftui/61" target="_blank" rel="noopener"&gt;#100DaysOfSwiftUI&lt;/a&gt; . The task was to suck up some data in JSON, decode it. back it up into a Core Data graph then display the data from the Core Data.&lt;/p&gt;
&lt;p&gt;I got stuck on dealing with the one:many relationship and had to revisit that from a different source to get my head around it, after that it was straightforward. Other small problem I ran into was that I created the id in the CachedUser as a UUID from (newly formed) habit. Then when I went to copy it in from the JSON version, it wouldn&amp;rsquo;t let me. When I realised my mistake and changed it in the data model, I still could not figure out why it wasn&amp;rsquo;t working - but of course I hadn&amp;rsquo;t regenerated the code for the ManagedObject. I just had to change the property type in the already generated code from UUID to string and I was back in business.&lt;/p&gt;</description></item><item><title>Core Data basics – Part Three</title><link>https://devendevour.iankulin.com/core-data-basics-part-three/</link><pubDate>Thu, 24 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/core-data-basics-part-three/</guid><description>&lt;p&gt;If you&amp;rsquo;re just stumbling across this, perhaps have a look at Part 1 where I layout a simple master/detail app with the data held as arrays of structs, and Part 2 where I convert that into the simplest possible Core Data version. In this post, I&amp;rsquo;m going to add the mechanics for the one:many relationship - Each Garden can be associated with multiple Plants.&lt;/p&gt;
&lt;p&gt;I should also mention I figured out some of this with help from &lt;a href="https://www.youtube.com/watch?v=xgPlJXTfiNA" target="_blank" rel="noopener"&gt;this video&lt;/a&gt; from &lt;a href="https://www.youtube.com/channel/UCxnCA5FBYRCFgIZWD0CKCVg/about" target="_blank" rel="noopener"&gt;Jonathan Rasmusson&lt;/a&gt; .&lt;/p&gt;</description></item><item><title>Core Data basics - Part Two</title><link>https://devendevour.iankulin.com/core-data-basics-part-two/</link><pubDate>Wed, 23 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/core-data-basics-part-two/</guid><description>&lt;p&gt;Yesterday I roughed out a master/detail app with a list of gardens, and for each garden a detail screen including some plants. It used arrays of structs for the data. Today I&amp;rsquo;m going to convert that app to use Core Data, and explain my understanding of each step. This won&amp;rsquo;t be the entire app - I&amp;rsquo;m going to include the plants in my data structure, but not actually use them in this version. I&amp;rsquo;ll save that 1:many relationship stuff for another post.&lt;/p&gt;</description></item><item><title>Core Data basics - Part One</title><link>https://devendevour.iankulin.com/core-data-basics-part-one/</link><pubDate>Tue, 22 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/core-data-basics-part-one/</guid><description>&lt;p&gt;To help me get clear on the Core Data basics (&lt;a href="https://devendevour.iankulin.com/tough-day/"&gt;so I can master one of the #100Days challenges&lt;/a&gt; ), I&amp;rsquo;ll write a simple master/detail app with arrays of structs, then convert it to Core Data listing out of the steps. Almost everything I know about Core Data, I learned from Paul Hudson&amp;rsquo;s &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;100 Days of SwiftUI&lt;/a&gt; course - of which I&amp;rsquo;m up to day 61. So shout out to him. I highly recommend that course, and most of the code you&amp;rsquo;ll see in this post is either inspired by, or directly copied from 100 Days, except of course the errors - those are mine. This post - Part One - just describes the app and shows the struct/array version.&lt;/p&gt;</description></item><item><title>Something weird 'append</title><link>https://devendevour.iankulin.com/something-weird-about-append/</link><pubDate>Mon, 21 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/something-weird-about-append/</guid><description>&lt;p&gt;I&amp;rsquo;m noodling around making sure I understand how Core Data works. Thought I&amp;rsquo;d start with a master/detail app with an array of structs, then replicate it in a Core Data implementation. I&amp;rsquo;m using an array of this struct for my data:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;struct Garden {
 var id = UUID()
 var name = &amp;#34;&amp;#34;
 var address = &amp;#34;&amp;#34;
 var plants: [Plant] = []
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And I thought this code to load up some sample data was pretty sweet.&lt;/p&gt;</description></item><item><title>Tough Day</title><link>https://devendevour.iankulin.com/tough-day/</link><pubDate>Sun, 20 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/tough-day/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/bad-day-at-sea-uspaceken.jpg" alt="" class="img-responsive"&gt; 
&lt;em&gt;&lt;a href="https://www.reddit.com/r/Art/comments/7i7crd/bad_day_at_sea_ii_30_x_40_oil/" target="_blank" rel="noopener"&gt;Bad Day at Sea - reddit u/SpaceKen&lt;/a&gt; &lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Day 61 of &lt;a href="https://www.hackingwithswift.com/100/swiftui/61" target="_blank" rel="noopener"&gt;#100DaysOfSwiftUI&lt;/a&gt; is a tough day. It&amp;rsquo;s the first real big test of Core Data understanding, and I&amp;rsquo;m finding I didn&amp;rsquo;t actually understand how the code for Core Data works. For the first time, I&amp;rsquo;m thinking of going back and redoing the days leading up to it.&lt;/p&gt;
&lt;p&gt;To try and get it straight in my mind, here&amp;rsquo;s how I think Core Data works:&lt;/p&gt;</description></item><item><title>iOS 16 Developer Mode</title><link>https://devendevour.iankulin.com/ios-16-developer-mode/</link><pubDate>Sat, 19 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ios-16-developer-mode/</guid><description>&lt;p&gt;I updated my iPhone to iOS16 this morning, and tonight when I went to run one of my apps, it complained that it needed Developer mode. This is a new, probably wise, way to avoid dodgy apps being loaded on a phone. I don&amp;rsquo;t know exactly how you&amp;rsquo;d do that, but then I&amp;rsquo;m not a black hatted cyber terrorist.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_3319.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I had to google the setting, it&amp;rsquo;s in &amp;ldquo;Privacy and Security&amp;rdquo; down the bottom, and requires a reboot. When you open the phone there&amp;rsquo;s another dialog and you need to reauth.&lt;/p&gt;</description></item><item><title>iOS Dev Twitter</title><link>https://devendevour.iankulin.com/ios-dev-twitter/</link><pubDate>Fri, 18 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ios-dev-twitter/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/twitter-logo-bird-bleeding.jpg" alt="Twitter logo bird bleeding - stable diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;One of &lt;a href="https://www.youtube.com/c/SeanAllen/videos" target="_blank" rel="noopener"&gt;Sean Allen&amp;rsquo;s&lt;/a&gt; many pieces of excellent advice was to follow a few Swift/iOS dev people on Twitter. I took that advice, and it is now a source of joy to flick through every couple of days and get a feel for what&amp;rsquo;s happening, and to discover new things. It&amp;rsquo;s how I learned &lt;a href="https://iosdevweekly.com/" target="_blank" rel="noopener"&gt;iOS Dev Weekly&lt;/a&gt; existed, and discovered &lt;a href="https://designcode.io/instructor/meng" target="_blank" rel="noopener"&gt;Meng To&lt;/a&gt; , and put faces/ideas to Swift and iOS people that&amp;rsquo;s I&amp;rsquo;d heard mentioned or interviewed in podcasts such as &lt;a href="https://ericasadun.com/" target="_blank" rel="noopener"&gt;Erica Sadun&lt;/a&gt; and &lt;a href="https://sarunw.com/" target="_blank" rel="noopener"&gt;Sarun W.&lt;/a&gt; &lt;/p&gt;</description></item><item><title>Clean Build Folder</title><link>https://devendevour.iankulin.com/clean-build-folder/</link><pubDate>Thu, 17 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/clean-build-folder/</guid><description>&lt;p&gt;Working on adding Core Data to the FriendFace app, and burnt up 20 minutes figuring out a bug. To set the scene, all I&amp;rsquo;ve changed in the app is to add a couple of core data entities. The plan is that when the JSON is fetched, and decoded into the objects, a copy of the graph will be persisted.&lt;/p&gt;
&lt;p&gt;Problem One was that I was getting a build errors saying the core data classes had been re-declared, and others saying that my class name was ambiguous. Since XCode had generated this code when I&amp;rsquo;d told it to &amp;ldquo;Create NSManagedObject subclass&amp;rdquo;. This is what you do when you want to be able to edit the NSManagedObject for example to created computed properties to unwrap the real properties. If you don&amp;rsquo;t need that flexibility, you just leave the default setting in the entity for XCode to create internally.&lt;/p&gt;</description></item><item><title>Console spam - No wall clock alignment</title><link>https://devendevour.iankulin.com/console-spam-no-wall-clock-alignment/</link><pubDate>Wed, 16 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/console-spam-no-wall-clock-alignment/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-11-12-at-8.02.28-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;When I was working on the Day 60 app, I noticed I kept getting a message in the console &amp;ldquo;&lt;code&gt;No wall clock alignment provided at SwiftUI/ResolvableStringAttribute.swift:86&lt;/code&gt;&amp;rdquo; every time I went into the detail view. Via elimination by commenting bits out, I&amp;rsquo;ve narrowed it down to a date formatting call. Here is the code to reproduce it in Xcode Version 14.0.1, Swift 5.7.0.127.4&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;struct ContentView: View {
 var body: some View {
 Text(&amp;#34;Date: \(Date(), style: .date)&amp;#34;)
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It&amp;rsquo;s to do with the style - if I change it to .time or .relative the message does not appear.&lt;/p&gt;</description></item><item><title>FriendFace Feedback</title><link>https://devendevour.iankulin.com/friendface-feedback/</link><pubDate>Tue, 15 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/friendface-feedback/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-11-12-at-4.38.24-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;After each app, I use my HackingWithSwift+ membership to view Paul&amp;rsquo;s version of the app as a way to judge my performance. Yesterday&amp;rsquo;s app was &amp;ldquo;&lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/5/3/challenge" target="_blank" rel="noopener"&gt;FriendFace&lt;/a&gt; &amp;rdquo; - download some JSON of a number of people (including their friends) and display it.&lt;/p&gt;
&lt;h4 id="uuid"&gt;UUID&lt;/h4&gt; &lt;p&gt;In my struct, I&amp;rsquo;d just specified the User.id as a string, Paul uses UUID - this makes no difference to the app as it stands, but is much better if we ever needed to add users.&lt;/p&gt;</description></item><item><title>FriendFace</title><link>https://devendevour.iankulin.com/friendface/</link><pubDate>Mon, 14 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/friendface/</guid><description>&lt;p&gt;The &lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/5/3/challenge" target="_blank" rel="noopener"&gt;Day 60 Milestone&lt;/a&gt; is a demo app that vacuums up some JSON and displays it in a list in a NavigationView that links to a details page. Nothing super strenuous, the steps were something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download the JSON and have a look at the structure. Firefox has a simple JSON viewer built in, so it was straightforward to see this is an array of users, which along with some (mostly string) properties contains an array of tag strings, and another array of friends.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-11-12-at-3.23.28-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;</description></item><item><title>Profile Photo Rabbit Hole</title><link>https://devendevour.iankulin.com/profile-photo-rabbit-hole/</link><pubDate>Sun, 13 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/profile-photo-rabbit-hole/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/down-the-rabbit-hole-childrens-book-illustration.jpg" alt="down the rabbit hole, children&amp;rsquo;s book illustration - stable diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m on &lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/5/3/challenge" target="_blank" rel="noopener"&gt;day 60 of #100Days&lt;/a&gt; , and have just wasted most of an evening&amp;rsquo;s coding time going down a rabit hole I didn&amp;rsquo;t need to. The app for this challenge is called &amp;ldquo;FriendFace&amp;rdquo; and is pretty straightforward: download a heap of JSON which is an array of users. Show it in a list that can be clicked through to see the details of that user.&lt;/p&gt;</description></item><item><title>git stash</title><link>https://devendevour.iankulin.com/git-stash/</link><pubDate>Sat, 12 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/git-stash/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/mila2.jpg" alt="mila kunis standing in front of a bank vault, watercolor painting - stable diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;When I was writing the blog post for the last project, I needed the &amp;ldquo;before&amp;rdquo; code to paste into the post. I&amp;rsquo;d committed that code, so a quick way to go back without losing my changes. I hadn&amp;rsquo;t committed the new code, so there is a super easy way to accomplish this.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git stash
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This grabs the code since the last commit and stashes it away, reverting the directory to the last committed version. I was able to copy the code I needed to the blog post, then to go back to my changes:&lt;/p&gt;</description></item><item><title>Project 12 Feedback</title><link>https://devendevour.iankulin.com/project-12-feedback/</link><pubDate>Fri, 11 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/project-12-feedback/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/tataed.jpg" alt="adel and taylor swift with ed sheeran, watercolor painting - stable diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;As usual, I watch Paul&amp;rsquo;s solution video, and compare it to mine.&lt;/p&gt;
&lt;h4 id="task-1"&gt;Task 1&lt;/h4&gt; &lt;p&gt;This was passing in the predicate as a String. I passed the whole thing, but as I figured out along the way, Paul meant just the operator word. He also added some buttons to test it better, which I didn&amp;rsquo;t think of till Task 3 - it would have saved me some simulator runs.&lt;/p&gt;</description></item><item><title>Project 12 Challenges</title><link>https://devendevour.iankulin.com/project-12-challenges/</link><pubDate>Thu, 10 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/project-12-challenges/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/adelandco.jpg" alt="taylor swift with ed sheeran and adel, watercolor painting - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Project 12 was a series of code tutorials around developing CoreData concepts rather than a real app, but the &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/core-data-wrap-up" target="_blank" rel="noopener"&gt;challenges&lt;/a&gt; are based on a very small app that uses a subview to allow dynamic (ie changeable at runtime) filtering of a list of data. The reason this would be tricky is that the @FetchRequest is a property of a view - and therefore mutable. The trick is to have a subview to build that part of the view, and to pass parameters into it which build the fetchrequest using an underscore.&lt;/p&gt;</description></item><item><title>You Can Take Big Steps When You Feel Safe</title><link>https://devendevour.iankulin.com/you-can-take-big-steps-when-you-feel-safe/</link><pubDate>Wed, 09 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/you-can-take-big-steps-when-you-feel-safe/</guid><description>&lt;p&gt;&lt;a href="https://www.deviantart.com/jhonair/art/Forest-of-giantess-604262747" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/forest-of-giantess-jhonair.png" alt="" title="Forest-of-giantess By JhonAir" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/100/swiftui/58" target="_blank" rel="noopener"&gt;Day 58&lt;/a&gt; of &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;#100Days&lt;/a&gt; feels like complex topics are being dropped in pretty fast. We tackle one:many data relationships and how to set them up in CoreData, using CoreData constraints and setting a merge policy to manage conflicts, and even the underscore to access the actual property inside a wrapped property struct (needed for dynamic filtering in a view).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve &lt;a href="https://devendevour.iankulin.com/top-four-reasons-why-twostraws-is-a-good-teacher/"&gt;mentioned before&lt;/a&gt; that I think Paul Hudson is an excellent teacher, and an example of this is that even though this was a day with a lot of challenging material, I&amp;rsquo;m not worried. I followed the discussion and tried the code, and more importantly I&amp;rsquo;m anticipating these new skills will be practiced in the next app, and probably shortly after I&amp;rsquo;ll be writing an app using them.&lt;/p&gt;</description></item><item><title>git - Rollback to last commit</title><link>https://devendevour.iankulin.com/git-rollback-to-last-commit/</link><pubDate>Tue, 08 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/git-rollback-to-last-commit/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/girl-turning-around-out.jpg" alt="girl turning around, cartoon, colorful - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m on &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/dynamically-filtering-fetchrequest-with-swiftui" target="_blank" rel="noopener"&gt;Project 12&lt;/a&gt; of the &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;#100Days&lt;/a&gt; course, and like a number of earlier &amp;ldquo;projects&amp;rdquo; it&amp;rsquo;s not really a project, but a series of type-along tutorials. Often these have the same format - there&amp;rsquo;s a base amount of code to provide the setup, then this base is used to try each of the tutorial techniques. At the end of each technique, you delete all the new code you&amp;rsquo;ve done back to the original setup, and you&amp;rsquo;re ready for the next one.&lt;/p&gt;</description></item><item><title>Bookworm Feedback</title><link>https://devendevour.iankulin.com/bookworm-feedback/</link><pubDate>Mon, 07 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/bookworm-feedback/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/cartoon-worm-on-a-book-2.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I did so well on this one that it&amp;rsquo;s not going to make a very interesting post. My first two challenge solutions were pretty much character for character the same - so not much to report.&lt;/p&gt;
&lt;p&gt;On the third challenge, there was a minor difference in the display process. I had done this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;let date = book.date ?? Date()
Text(date.formatted(.dateTime.day().month().year()))
 .foregroundColor(.secondary)
 .opacity(date == book.date ? 1 : 0)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But @twostraws went:&lt;/p&gt;</description></item><item><title>Bookworm Challenges</title><link>https://devendevour.iankulin.com/bookworm-challenges/</link><pubDate>Sun, 06 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/bookworm-challenges/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/cartoon-worm-on-a-book.jpg" alt="cartoon worm on a book - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Another set of challenges for a &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;#100DaysofSwiftUI&lt;/a&gt; tutorial app. Project 11 was a book tracking app - the big new thing was using CoreData. Here&amp;rsquo;s the &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/bookworm-wrap-up" target="_blank" rel="noopener"&gt;challenges for it&lt;/a&gt; .&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Right now it’s possible to select no title, author, or genre for books, which causes a problem for the detail view. Please fix this, either by forcing defaults, validating the form, or showing a default picture for unknown genres – you can choose.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>@Binding - data between views</title><link>https://devendevour.iankulin.com/binding-data-between-views/</link><pubDate>Sat, 05 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/binding-data-between-views/</guid><description>&lt;p&gt;In C world, if we want to pass a parameter down into a functional call, and allow the receiving function to change it&amp;rsquo;s value, we&amp;rsquo;d pass a pointer to the variable. Something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

void increment(int* b) {
 *b=*b+1;
}

int main() {
 int a = 5;
 increment(&amp;amp;a);
 printf(&amp;#34;%d&amp;#34;, a);
 return 0;
}

// prints &amp;#39;6&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For youngsters, what&amp;rsquo;s happening is that we&amp;rsquo;ve set the value of a to 5, then passed the memory &lt;em&gt;address&lt;/em&gt; of a into the increment() function. That&amp;rsquo;s what the @a means.&lt;/p&gt;</description></item><item><title>CoreData and the Preview</title><link>https://devendevour.iankulin.com/coredata-and-the-preview/</link><pubDate>Fri, 04 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/coredata-and-the-preview/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/looking-through-a-keyhole-to-a-room-diagram-colorful.jpg" alt="looking through a keyhole to a room, diagram, colorful - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve noticed Paul is inclined to ignore the preview and run his code in the simulator to check its operation. That&amp;rsquo;s valid, but it seems quicker, and reassuring, to see it in the preview as I type.&lt;/p&gt;
&lt;p&gt;This led to a small problem with &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/how-to-combine-core-data-and-swiftui" target="_blank" rel="noopener"&gt;Day 53&lt;/a&gt; that uses CoreData. When I added a student in the preview, it looked like this, and was immediately followed with a crash report.&lt;/p&gt;</description></item><item><title>Cupcake Corner Feedback</title><link>https://devendevour.iankulin.com/cupcake-corner-feedback/</link><pubDate>Thu, 03 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/cupcake-corner-feedback/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/cute-cupcake-cartoon-drawing.jpg" alt="cute cupcake, cartoon drawing - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;As usual, here&amp;rsquo;s my thoughts comparing my attempts at the challenges to Paul&amp;rsquo;s. Usually he&amp;rsquo;s better!&lt;/p&gt;
&lt;h4 id="1-whitespace"&gt;1) Whitespace&lt;/h4&gt; &lt;p&gt;The task was to validate the order address properties, not just by checking they are not empty, but also that they don&amp;rsquo;t just contain spaces. I went the bruteforce route since there was no .isEmptyIncludingWhitespace method.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;var hasValidAddress: Bool {
 let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
 let trimmedStreetAddress = streetAddress.trimmingCharacters(in: .whitespacesAndNewlines)
 let trimmedCity = city.trimmingCharacters(in: .whitespacesAndNewlines)
 let trimmedZip = zip.trimmingCharacters(in: .whitespacesAndNewlines)

 if trimmedName.isEmpty || trimmedStreetAddress.isEmpty || trimmedCity.isEmpty || trimmedZip.isEmpty {
 return false
 }
 return true
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As soon as Paul mentioned extending String, I facepalmed - of course, just create the method I want on string. Paul&amp;rsquo;s is a one line extension - neater, and Swiftyier.&lt;/p&gt;</description></item><item><title>Cupcake Corner challenges</title><link>https://devendevour.iankulin.com/cupcake-corner-challenges/</link><pubDate>Wed, 02 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/cupcake-corner-challenges/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/cupcake.jpg" alt="cute cupcake, cartoon - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/cupcake-corner-wrap-up" target="_blank" rel="noopener"&gt;Day 52&lt;/a&gt; of &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;#100Days&lt;/a&gt; was the challenges to the Cupcake Corner app - an app that allows you to build a one-row order, encode it as JSON and submit it to an API with a URLSession. To allow the order to be passed around, it&amp;rsquo;s an @ObservedObject which meant that a few extra hoops needed to be jumped through to make it Codable.&lt;/p&gt;
&lt;h4 id="1-whitespace-validation"&gt;1) Whitespace validation&lt;/h4&gt; &lt;p&gt;The tutorial app validates the order address by checking that each field is not empty, but it can be fooled by just entering some spaces. The first challenge was to fix that.&lt;/p&gt;</description></item><item><title>Top Four Reasons why @TwoStraws is a Good Teacher</title><link>https://devendevour.iankulin.com/top-four-reasons-why-twostraws-is-a-good-teacher/</link><pubDate>Tue, 01 Nov 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/top-four-reasons-why-twostraws-is-a-good-teacher/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-10-29-at-1.28.59-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;h4 id="good-questions"&gt;Good Questions&lt;/h4&gt; &lt;p&gt;At various points in the &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;100 Days of SwiftUI&lt;/a&gt; course, you get asked sets of questions to check you&amp;rsquo;ve understood the preceding material. They&amp;rsquo;re usually presented as two different statements, one of which is true, and the other false. It&amp;rsquo;s actually a really good technique - the student feels like they&amp;rsquo;ve got a couple of opportunities to figure it out, plus they are forced to read both statements and think about them. Paul does a similar thing in the Unwrapped app - there, the questions are often presented as &amp;ldquo;Is this valid Swift code&amp;rdquo; and the user needs to scan through it all looking for mistakes. It&amp;rsquo;s checking your understanding, and making you a thoughtful debugger!&lt;/p&gt;</description></item><item><title>Codable when the keys don't match</title><link>https://devendevour.iankulin.com/codable-when-the-keys-dont-match/</link><pubDate>Mon, 31 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/codable-when-the-keys-dont-match/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/medieval-door-lock-detailed-drawing.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A common issue when working with JSON that you vacuum up from internet APIs will be that the key names in the JSON don&amp;rsquo;t match your property names. The JSON de facto standard of using snake_case in key names could be one cause, or perhaps you just take &lt;a href="https://www.freshconsulting.com/insights/blog/development-principle-1-choose-appropriate-variable-names/" target="_blank" rel="noopener"&gt;variable naming more seriously&lt;/a&gt; than the person who wrote the API.&lt;/p&gt;
&lt;p&gt;We saw yesterday how using codable and the JSONEncoder in Swift makes moving between an object/struct in the code and a stringish representation of it simple. With a couple of small changes, we can also deal with the mismatched key/property name issue.&lt;/p&gt;</description></item><item><title>Codable &amp;amp; JSON</title><link>https://devendevour.iankulin.com/codable-json/</link><pubDate>Sun, 30 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/codable-json/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/encryption-machine-comic-27970-.jpg" alt="encryption machine, comic 27970 - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;If we mark a type with the protocol &lt;em&gt;Codable&lt;/em&gt;, we&amp;rsquo;re specifying that this type has the capability of having it&amp;rsquo;s properties encoded to some format, and decoded back again.&lt;/p&gt;
&lt;p&gt;So far in the #100Days this has been used to write and read data in UserDefaults, and to encode an object to send it as a URLRequest, then receive data back and create a new object from it. It&amp;rsquo;s a handy, powerful feature baked into Swift that just requires the developer to ensure any types that need this functionality comply with the &lt;em&gt;Encodable&lt;/em&gt; and &lt;em&gt;Decodable&lt;/em&gt; protocols that make up the Codable.&lt;/p&gt;</description></item><item><title>Git - make all the commits into a single commit</title><link>https://devendevour.iankulin.com/git-make-all-the-commits-into-a-single-commit/</link><pubDate>Sat, 29 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/git-make-all-the-commits-into-a-single-commit/</guid><description>&lt;p&gt;When I&amp;rsquo;m following a tutorial app, I generally pause and type up the code as I go, and make local commits with appropriate messages. This is almost completely unnecessary, but it seems like a good habit and doesn&amp;rsquo;t cost me anything - I just tick the box for creating the git when I start the project, then it&amp;rsquo;s a couple of keystrokes (option-command-C) and I&amp;rsquo;m done.&lt;/p&gt;
&lt;p&gt;Most of the apps have a follow-along portion, then some challenges which involve minor changes to the app. When I get to the challenges I like to throw it up on Github - it&amp;rsquo;s conceivable it could help someone one day, or at the least, I&amp;rsquo;m helping to train &lt;a href="https://github.com/features/copilot" target="_blank" rel="noopener"&gt;Microsoft&amp;rsquo;s AI&lt;/a&gt; to write shitty beginner code in exchange for free git server access.&lt;/p&gt;</description></item><item><title>Day 50 - @State vs @Observed again</title><link>https://devendevour.iankulin.com/day-50-state-vs-obseved-again/</link><pubDate>Fri, 28 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/day-50-state-vs-obseved-again/</guid><description>&lt;p&gt;Way back when, I was unclear about @StateObject and @ObservedObject (&lt;a href="https://devendevour.iankulin.com/simple-mvvm/"&gt;here&lt;/a&gt; , and &lt;a href="https://devendevour.iankulin.com/observedobject-v-stateobject/"&gt;here&lt;/a&gt; ). I still am.&lt;/p&gt;
&lt;p&gt;But in &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/taking-basic-order-details" target="_blank" rel="noopener"&gt;today&amp;rsquo;s tutorial&lt;/a&gt; video, Paul clearly says that the @StateObject is for the single place in your app where the object is created, then everywhere else, use @ObservedObject. Trouble is, I just know from the Simple MVVM app I made if you wrap the single instance of the data model with the @ObservedObject, it still works.&lt;/p&gt;</description></item><item><title>Day 47 - Habits App</title><link>https://devendevour.iankulin.com/day-47-habits-app/</link><pubDate>Thu, 27 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/day-47-habits-app/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/tgirl-3.jpg" alt="pretty! woman holding a bag of trash, Artstation - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been mucking around with the Habits app too long - it&amp;rsquo;s started to look like procrastination. It already meets the &lt;a href="https://www.hackingwithswift.com/100/swiftui/47" target="_blank" rel="noopener"&gt;specification&lt;/a&gt; , so I&amp;rsquo;m calling it an MVP and moving on.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/Habitual" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/github-mark-32px.png" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_3110.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;This is the first app of mine I&amp;rsquo;ve loaded onto my phone and started using, and there are a couple of things I&amp;rsquo;d like to do with it. It currently just lets you specify how many days between an activity repeating - so if you say you should go to the gym every second day, and you complete that activity on Monday, &amp;ldquo;Gym&amp;rdquo; will make it&amp;rsquo;s way to the top of the list on Wednesday. While it&amp;rsquo;s waiting in the list for Wednesday to come around, it will show the &amp;ldquo;Due&amp;rdquo; time as being exactly 48 hours after you last pressed &amp;ldquo;done&amp;rdquo; on it. But if the habit you want is to go to the gym after work at 6:00pm that&amp;rsquo;s when you want it to be due. I&amp;rsquo;d like that.&lt;/p&gt;</description></item><item><title>Why?</title><link>https://devendevour.iankulin.com/why/</link><pubDate>Wed, 26 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/why/</guid><description>&lt;p&gt;Why do I have to resize this preview window every time I open Xcode?&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/y4imP93Czmc?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;</description></item><item><title>Updating stored JSON due to a struct change</title><link>https://devendevour.iankulin.com/updating-stored-json-due-to-a-struct-change/</link><pubDate>Tue, 25 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/updating-stored-json-due-to-a-struct-change/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/jason-modern-art.jpg" alt="Jason Modern Art - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I mentioned yesterday &amp;ldquo;&lt;em&gt;I could use a renamed old version of my struct to load the existing data, and convert it across to the new model.&lt;/em&gt;&amp;rdquo;. Since I&amp;rsquo;ve been testing the app on my phone, and using plausible data, it was going to be painful enough to lose it that I thought I should go through those steps.&lt;/p&gt;
&lt;p&gt;First, I make a copy of the old struct, and renamed it with the app version number that used it. No need to bring all the computed properties into this struct, just the bits that get encoded into the JSON.&lt;/p&gt;</description></item><item><title>JSON encode/decode</title><link>https://devendevour.iankulin.com/json-encode-decode/</link><pubDate>Mon, 24 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/json-encode-decode/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_3110.png" alt="Screenshop of Habits app" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;As usual, I&amp;rsquo;m spending way more time on the apps written from scratch in the &lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/4/3/challenge" target="_blank" rel="noopener"&gt;100Days series&lt;/a&gt; . The Habit tracking app I&amp;rsquo;m working on has been good practice, especially of the architecture of the simple &lt;a href="https://devendevour.iankulin.com/list-apps/"&gt;list based app&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;My version has a couple of refinements I quite like. I&amp;rsquo;m using a checkmark in a rectangle as the button to mark that activity as done, and I&amp;rsquo;ve added a nice fade to the checkmark as time goes on to represent the percentage of time from when it is done until it becomes due again.&lt;/p&gt;</description></item><item><title>Refreshing SwiftUI Views</title><link>https://devendevour.iankulin.com/refreshing-swiftui-views/</link><pubDate>Sun, 23 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/refreshing-swiftui-views/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/refreshing-view-rococo.jpg" alt="refreshing view, Rococo - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;SwiftUI does some property wrapper magic to (very efficiently) refresh your views, but what if you want to force a refresh for some reason? Here&amp;rsquo;s the techniques I&amp;rsquo;m currently using to do that.&lt;/p&gt;
&lt;p&gt;The tricks are below, but just so you can see them in context, here&amp;rsquo;s the sample app we&amp;rsquo;re working on. It&amp;rsquo;s a list of cars so you can keep track of how many of each kind you own. Here&amp;rsquo;s our data:&lt;/p&gt;</description></item><item><title>You need to enjoy puzzles</title><link>https://devendevour.iankulin.com/you-need-to-enjoy-puzzles/</link><pubDate>Sat, 22 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/you-need-to-enjoy-puzzles/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/frustration.jpg" alt="frustrated worker, painting - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m writing the Habits &lt;a href="https://devendevour.iankulin.com/list-apps/"&gt;list based app&lt;/a&gt; from #100Days and had a working MVP, then for some reason, decided to refactor by changing the subview I&amp;rsquo;d written as a function, into a struct. Some time later, I discovered that my list items were not updating correctly, so detective time.&lt;/p&gt;
&lt;p&gt;I talked a little bit about the architecture yesterday - the item is a struct, and there&amp;rsquo;s a class containing an array of the items. Something like this:&lt;/p&gt;</description></item><item><title>Purple warning - "Publishing changes"</title><link>https://devendevour.iankulin.com/purple-warning-publishing-changes/</link><pubDate>Fri, 21 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/purple-warning-publishing-changes/</guid><description>&lt;p&gt;It&amp;rsquo;s a pretty safe bet that if Xcode is saying there&amp;rsquo;s an error in my code, that it&amp;rsquo;s correct, and I am in error - not Xcode. Today I came across a situation where that might not be true.&lt;/p&gt;
&lt;p&gt;I think the purple warnings are problems detected at runtime - I&amp;rsquo;ve heard of thread problems causing purple warnings. The error I was getting was &amp;ldquo;&lt;code&gt;Publishing changes from within view updates is not allowed, this will cause undefined behavior.&lt;/code&gt;&amp;rdquo;&lt;/p&gt;</description></item><item><title>List Apps</title><link>https://devendevour.iankulin.com/list-apps/</link><pubDate>Thu, 20 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/list-apps/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/girl-making-a-list-at-a-desk-graphic-novel.jpg" alt="girl making a list at a desk, graphic novel - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;When I was first programming professionally, it wasn&amp;rsquo;t long before I noticed that there were patterns to the sort of bread-and-butter things I was writing most times - the majority of the small business applications I wrote tracked several entities; for each entity there needed to be add/edit/delete screens, there would be some business rules around those things, and some reports and search functionality.&lt;/p&gt;</description></item><item><title>Drawing Feedback</title><link>https://devendevour.iankulin.com/drawing-feedback/</link><pubDate>Wed, 19 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/drawing-feedback/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/hedylamarrstandinginfront_52901467.jpg" alt="Hedy Lamarr standing in front of a drawing, Kuvshinov Ilya - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the summary of my learning from comparing &lt;a href="https://devendevour.iankulin.com/project-9-drawing/"&gt;my efforts&lt;/a&gt; with Paul&amp;rsquo;s solutions to the Project Nine challenges from &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/drawing-wrap-up" target="_blank" rel="noopener"&gt;Day 46&lt;/a&gt; of his &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;100 Days of SwiftUI course&lt;/a&gt; .&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Create an &lt;code&gt;Arrow&lt;/code&gt; shape – having it point straight up is fine. This could be a rectangle/triangle-style arrow, or perhaps three lines, or maybe something else depending on what kind of arrow you want to draw.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Project 9 - Drawing</title><link>https://devendevour.iankulin.com/project-9-drawing/</link><pubDate>Tue, 18 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/project-9-drawing/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-10-16-at-12.17.46-pm.jpg" alt="Screenshot of Xcode and the preview showing some fancy graphics" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;These few days of &lt;a href="https://www.hackingwithswift.com/100/swiftui/43" target="_blank" rel="noopener"&gt;#100DaysOfSwiftUI&lt;/a&gt; we made some pretty shapes by playing around with some of the SwiftUI systems for drawing on the screen, including paths, shapes, transformations, ImagePaint, drawingGroup() to use Metal rendering, blurs, blend modes and using animatableData for animating - which I think is the solution to an animation problem in my TimesTable app I hadn&amp;rsquo;t been able to solve yet.&lt;/p&gt;</description></item><item><title>When it Works</title><link>https://devendevour.iankulin.com/when-it-works/</link><pubDate>Mon, 17 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/when-it-works/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-10-15-at-10.43.18-am.jpg" alt="Screenshot of swiftui code and the iphone simulator with a roughly drawn face" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The little joy of something working. It&amp;rsquo;s one of the things that makes coding enjoyable. Like a good video game you have an overarching goal, but on the way you need to solve a large number of problems of variable complexity, and you get a little bit of dopamine for each one.&lt;/p&gt;
&lt;p&gt;I think in every language I&amp;rsquo;ve ever learned, as soon as I know how to draw something on the screen, I start to get the urge to create a simple drawing application. When I was starting on Visual C++ and the MFC (Microsoft Foundation Classes) the &lt;a href="https://www.amazon.com/Beginning-Visual-C-Ivor-Horton/dp/1861000081" target="_blank" rel="noopener"&gt;book&lt;/a&gt; I used to get started built a drawing application as an example. It hooks into the benefit of being able to quickly see the evidence you&amp;rsquo;ve achieved something.&lt;/p&gt;</description></item><item><title>Musings on Data</title><link>https://devendevour.iankulin.com/musings-on-data/</link><pubDate>Sun, 16 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/musings-on-data/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/thinking-about-data-drawing.jpg" alt="Thinking about data - drawing. Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been feeling my enthusiasm for the online courses I started waning a little, additionally, I have enough progress under my belt that I could actually start working on some of the projects in my &amp;ldquo;App Ideas&amp;rdquo; notebook. I&amp;rsquo;m not sure if starting on them is a smart idea, or just a way of procrastinating. One thing most of those apps have in common, and that I haven&amp;rsquo;t substantively learned yet is persisting data. Their data requirements vary from a sort of specialised todo list only required on that device, to relational data that needs to be live synced across devices including macOS and needs to support rolling back transactions to resolve conflicts.&lt;/p&gt;</description></item><item><title>Moonshot Feedback</title><link>https://devendevour.iankulin.com/moonshot-feedback/</link><pubDate>Sat, 15 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/moonshot-feedback/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/girl-with-a-headset-in-a-space-command-center-kuvshinov-ilya-mitsumayo.jpg" alt="Girl with a headset in a space command center, Kuvshinov Ilya, Mitsumayo - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve watched Paul&amp;rsquo;s solution to the &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/moonshot-wrap-up" target="_blank" rel="noopener"&gt;Moonshot challenges&lt;/a&gt; (the solutions are one of the perks of being a Hacking With Swift subscriber). When I&amp;rsquo;m solo learning like this its one of the few ways I can get any feedback on my coding, so I highly value it, and usually write one of these posts as a way to ensure I reflect on it.&lt;/p&gt;</description></item><item><title>Moonshot Challenges</title><link>https://devendevour.iankulin.com/moonshot-challenges/</link><pubDate>Fri, 14 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/moonshot-challenges/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-10-09-at-2.00.26-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Another few coding challenges at the end of a tutorial app in the &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/moonshot-wrap-up" target="_blank" rel="noopener"&gt;100 Days of SwiftUI&lt;/a&gt; course. The app is a sort of information app - composed of navigation views going down into more detail about the Apollo space missions. The most exciting revelation for me was how straightforward it is to pull JSON into your apps data structures.&lt;/p&gt;
&lt;h4 id="challenge-1"&gt;Challenge 1&lt;/h4&gt; &lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Add the launch date to &lt;code&gt;MissionView&lt;/code&gt;, below the mission badge. You might choose to format this differently given that more space is available, but it’s down to you.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Using Swift's map</title><link>https://devendevour.iankulin.com/using-swifts-map/</link><pubDate>Thu, 13 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/using-swifts-map/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/untitled-2.jpg" alt="&amp;ldquo;map, moon, transformation&amp;rdquo; - Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;In &lt;a href="https://www.hackingwithswift.com/100/swiftui/39" target="_blank" rel="noopener"&gt;Day 39&amp;rsquo;&lt;/a&gt; s Moonshot tutorial app, Paul uses &lt;a href="https://developer.apple.com/documentation/swift/array/map%5c%28_:%5c%29-87c4d" target="_blank" rel="noopener"&gt;&lt;code&gt;.map&lt;/code&gt;&lt;/a&gt; on an array without much comment about what&amp;rsquo;s going on. I assume this might be a common concept in modern languages, but it was new to me.&lt;/p&gt;
&lt;p&gt;First, here&amp;rsquo;s Paul&amp;rsquo;s code&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;init&lt;/span&gt;(mission: Mission, astronauts: [String: Astronaut]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;self&lt;/span&gt;.mission = mission
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;self&lt;/span&gt;.crew = mission.crew.map { member &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; astronaut = astronauts[member.name] {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; CrewMember(role: member.role, astronaut: astronaut)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fatalError(&lt;span style="color:#e6db74"&gt;&amp;#34;Missing &lt;/span&gt;&lt;span style="color:#e6db74"&gt;\(&lt;/span&gt;member.name&lt;span style="color:#e6db74"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Mission&lt;/code&gt; here contains an array of &lt;code&gt;crew&lt;/code&gt; which is a struct with two strings, one of them being &lt;code&gt;name&lt;/code&gt;, but &lt;code&gt;self.crew&lt;/code&gt; (which belongs to the view we&amp;rsquo;re in) is an array of &lt;code&gt;CrewMember&lt;/code&gt; which is a struct with a &lt;code&gt;role: String&lt;/code&gt; and another struct &lt;code&gt;astronaut&lt;/code&gt;. That sounds confusing, but essentially, an array of one type is being processed to return an array of another type where there&amp;rsquo;s a 1:1 relationship between the elements of each array.&lt;/p&gt;</description></item><item><title>iExpense Feedback</title><link>https://devendevour.iankulin.com/iexpense-feedback/</link><pubDate>Wed, 12 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/iexpense-feedback/</guid><description>&lt;p&gt;I finally got around to looking at Paul&amp;rsquo;s solutions for the &lt;a href="https://devendevour.iankulin.com/iexpense-challenges/"&gt;iExpense challenges&lt;/a&gt; .&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use the user’s preferred currency, rather than always using US dollars.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Same approach as me,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.currency(code: Locale.current.currency?.identifier ?? &amp;quot;USD&amp;quot;)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;except that he does the work in a local variable which is a bit neater. Since it appears in two places - the display view and the view for adding an expense, this would mean duplicating it, making it global (not rare for user default type settings) or passing it down through the view hierarchy. I feel either of the first two options are fine for this project, but Paul is thorough and extends the FormatStyle protocol, only for currency, to have a new computed property .localcurrency - which is a great solution.&lt;/p&gt;</description></item><item><title>Using AI to Generate Icons</title><link>https://devendevour.iankulin.com/using-ai-to-generate-icons/</link><pubDate>Tue, 11 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/using-ai-to-generate-icons/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/macos-icon-scissors-floating-over-code2.jpg" alt="Open macbook with design image showing - created by Stable Diffusion" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Since I have minimal design skills, I went back to &lt;a href="https://www.fiverr.com/" target="_blank" rel="noopener"&gt;Fiverr&lt;/a&gt; (the digital gig economy platform) to get some icons done for CodeTrimmer - explaining that I wanted something like a &amp;ldquo;pair of scissors floating over some computer code&amp;rdquo;. At the same time I&amp;rsquo;ve been playing with &lt;a href="https://github.com/divamgupta/diffusionbee-stable-diffusion-ui" target="_blank" rel="noopener"&gt;DiffusionBee&lt;/a&gt; - a free Apple silicon version of the &lt;a href="https://stability.ai/blog/stable-diffusion-public-release" target="_blank" rel="noopener"&gt;Stable Diffusion&lt;/a&gt; artifical intellligence that generates images from text prompts. The image above was created on an M1 Macbook using DiffusionBee.&lt;/p&gt;</description></item><item><title>SwiftLint</title><link>https://devendevour.iankulin.com/swiftlint/</link><pubDate>Mon, 10 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/swiftlint/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screenshot-2022-10-04-at-08-30-59-code-complete-mcconnell-steve-amazon.com_.au-books.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I was watching a &lt;a href="https://www.techwithtim.net/" target="_blank" rel="noopener"&gt;Tim Ruscica&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=wJNikDr-aNM" target="_blank" rel="noopener"&gt;video&lt;/a&gt; about the things that highly effective developers do, and it called to mind a book I read years ago called &lt;a href="https://www.amazon.com.au/Code-Complete-Steve-McConnell/dp/0735619670" target="_blank" rel="noopener"&gt;Code Complete&lt;/a&gt; . It is the only book I ever owned that I immediately purchased the new edition when it came out. It was about the meta stuff around programming that is the difference between coding and developing. In particular, it got me invested in source control and testing.&lt;/p&gt;</description></item><item><title>Testing, testing</title><link>https://devendevour.iankulin.com/testing-testing/</link><pubDate>Sun, 09 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/testing-testing/</guid><description>&lt;p&gt;I have unit testing in my &lt;a href="https://devendevour.wordpress.com/goals/" target="_blank" rel="noopener"&gt;list of goals&lt;/a&gt; , and if I&amp;rsquo;m going to throw this &lt;a href="https://devendevour.iankulin.com/codetrimmer-first-macos-app/"&gt;space trimming&lt;/a&gt; macOS utility up on the web, now might be a good time to figure out how to add unit tests to a project, how to write them, and how to run them. XCode is well set up for this, so it&amp;rsquo;s really no drama to do.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-10-03-at-9.09.32-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Although I haven&amp;rsquo;t worried with any unit testing up to this point in my iOS/Swift learning, in my previous programming work I did a lot of work with the large calculations involved in translating GPS coordinates and robotic positioning models where errors would be bad - so I&amp;rsquo;ve written a lot of tests over the years. I&amp;rsquo;ve also definitely felt the confidence you can dramatically refactor code with when you know the code has a robust test suite. I&amp;rsquo;m a big fan.&lt;/p&gt;</description></item><item><title>Where's My App?</title><link>https://devendevour.iankulin.com/wheres-my-app/</link><pubDate>Sat, 08 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/wheres-my-app/</guid><description>&lt;p&gt;The iOS apps I&amp;rsquo;ve been making, can only run in the simulator or on my tethered device (which I haven&amp;rsquo;t actually tried yet), but the MacOS app I made today, in theory could be zipped up and distributed to the world from my website. At the very least, I wanted to drop it into my Applications folder so I could use it, so I needed to find the .app &amp;ldquo;file&amp;rdquo;, and realised I had no idea where it was. If that&amp;rsquo;s your situation, then here&amp;rsquo;s the steps you need.&lt;/p&gt;</description></item><item><title>Customizing the default About dialog for MacOS apps</title><link>https://devendevour.iankulin.com/customizing-the-default-about-dialog-for-macos-apps/</link><pubDate>Fri, 07 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/customizing-the-default-about-dialog-for-macos-apps/</guid><description>&lt;p&gt;The default Xcode MacOS targeted app has a built in &amp;ldquo;About&amp;rdquo; dialog called up from the &amp;ldquo;About &lt;app name&gt;&amp;rdquo; menu item in the Mac menu bar. It wasn&amp;rsquo;t immediately clear to me how to customise this, but after digging through some MacOS apps on GitHub, here&amp;rsquo;s the answer.&lt;/p&gt;
&lt;p&gt;When you app is being built, it looks for the file &amp;ldquo;Credits.rtf&amp;rdquo; in the app bundle. If that is found (&lt;a href="https://developer.apple.com/documentation/appkit/nsapplication/aboutpaneloptionkey/2869609-credits" target="_blank" rel="noopener"&gt;or &amp;ldquo;Credits.html&amp;rdquo; or &amp;ldquo;Credits.rtfd&amp;rdquo;&lt;/a&gt; ) it&amp;rsquo;s used to build out the About dialog along with your app icon.&lt;/p&gt;</description></item><item><title>CodeTrimmer - First MacOS App</title><link>https://devendevour.iankulin.com/codetrimmer-first-macos-app/</link><pubDate>Thu, 06 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/codetrimmer-first-macos-app/</guid><description>&lt;p&gt;I was listening to the StackTrace app this morning (&lt;a href="https://stacktracepodcast.fm/episodes/169/" target="_blank" rel="noopener"&gt;episode 169 - &amp;ldquo;Choosing What Bugs to Ship&amp;rdquo;&lt;/a&gt; ) and one of the ideas discussed was taking the time to automate some of your development processes, partially to save time, but also because if you make a process simple and quick, you&amp;rsquo;ll be more likely to do it multiple times to improve quality.&lt;/p&gt;
&lt;p&gt;Coincidentally, I&amp;rsquo;d been thinking about how often I paste some code from Xcode in order to display it in one of these blog posts. If it&amp;rsquo;s from the middle of a method, it will generally be indented a long way in, and there&amp;rsquo;s no point in displaying it like that (especially for a mobile reader) so I usually manually delete a heap of spaces from each line to left align it whilst keeping the needed indentation.&lt;/p&gt;</description></item><item><title>Design Challenge</title><link>https://devendevour.iankulin.com/design-challenge/</link><pubDate>Wed, 05 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/design-challenge/</guid><description>&lt;p&gt;So, I&amp;rsquo;ve been working on translating the &lt;a href="https://devendevour.iankulin.com/design-help/"&gt;UI design&lt;/a&gt; created by the external designer into SwiftUI, and have done all of the easy bits:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-10-03-at-8.19.43-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The rounded rectangles for things like the question display/number input are just ZStacks of roundedrects filled, then stroked:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ZStack {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RoundedRectangle(cornerRadius: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .fill(.white)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .padding(.horizontal)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RoundedRectangle(cornerRadius: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .stroke(.black, lineWidth: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .padding(.horizontal)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(questionText)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .font(.title)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .fontWeight(.heavy)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(calculatorDisplay)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .font(.title)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .fontWeight(.heavy)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .foregroundColor(.blue)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.frame(maxWidth: &lt;span style="color:#ae81ff"&gt;350&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.offset(y: &lt;span style="color:#ae81ff"&gt;15&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Something I have learned in the process is the .offset modifer. This is what&amp;rsquo;s used to move a view from where SwiftUI would have placed it, and is what I&amp;rsquo;ve done to create that overlapped style where the question display/number input is sitting halfway over the bottom of the blue rounded rectangle. This is in the last line of the code above: &lt;code&gt;.offset(y: 15)&lt;/code&gt; This is moving the whole ZStack down by 15. A trick to watch with this is that since you&amp;rsquo;ve messed with SwiftUI&amp;rsquo;s arrangement, it doesn&amp;rsquo;t then shuffle everything else around this - you need to manually deal with making some space below it.&lt;/p&gt;</description></item><item><title>Color Picker (website)</title><link>https://devendevour.iankulin.com/color-picker-website/</link><pubDate>Tue, 04 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/color-picker-website/</guid><description>&lt;p&gt;I&amp;rsquo;ve started work on trying to recreate a &lt;a href="https://devendevour.iankulin.com/design-help/"&gt;UI provided by a designer&lt;/a&gt; , and in the process needed to identify some colours from a PNG image. I found this great website for this exact purpose.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-30-at-4.36.17-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The site is ImageColorPicker. To use it, you &amp;ldquo;upload&amp;rdquo; your image (actually the image is not going anywhere - it&amp;rsquo;s all done in-browser). Then click on any area you want to identify the colour of.&lt;/p&gt;</description></item><item><title>Design Help</title><link>https://devendevour.iankulin.com/design-help/</link><pubDate>Mon, 03 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/design-help/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-29-at-5.50.43-pm-1.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I think I mentioned when I&amp;rsquo;d completed the &lt;a href="https://devendevour.iankulin.com/times-tables-day-35-challenge/"&gt;TimesTable app&lt;/a&gt; , that I was not happy with the design. It&amp;rsquo;s ugly, I don&amp;rsquo;t like the way the feedback about a wrong answer is shown at the same time as the next question, the number of questions setting would be rarely changed and looks uncomfortable where it is, I&amp;rsquo;m not sure the purpose of the picker for which times table would be clear, and it&amp;rsquo;s not appealing to children.&lt;/p&gt;</description></item><item><title>iExpense Challenges</title><link>https://devendevour.iankulin.com/iexpense-challenges/</link><pubDate>Sun, 02 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/iexpense-challenges/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-29-at-6.41.29-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/100/swiftui/38" target="_blank" rel="noopener"&gt;Day 38&lt;/a&gt; is three challenges on the iExpense app - a simple expense tracking app that uses UseDefaults for storing it&amp;rsquo;s data.&lt;/p&gt;
&lt;h3 id="locale"&gt;Locale&lt;/h3&gt; &lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use the user’s preferred currency, rather than always using US dollars.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the joys of modern programming (as opposed to mid-1990&amp;rsquo;s programming) is the ability of the internet to give you answers. I knew the answer to this would be lurking in the locale environment variable, but instead of &lt;a href="https://developer.apple.com/documentation/swiftui/environmentvalues/locale" target="_blank" rel="noopener"&gt;looking it up&lt;/a&gt; , just googled, and found a viable looking solution on &lt;a href="https://www.reddit.com/r/SwiftUI/comments/t7g7ds/localising_currency/" target="_blank" rel="noopener"&gt;Reddit&lt;/a&gt; .&lt;/p&gt;</description></item><item><title>Times Tables -Day 35 Challenge</title><link>https://devendevour.iankulin.com/times-tables-day-35-challenge/</link><pubDate>Sat, 01 Oct 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/times-tables-day-35-challenge/</guid><description>&lt;p&gt;The challenge for &lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/3/3/challenge" target="_blank" rel="noopener"&gt;Day 35&lt;/a&gt; of &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;100 Days of Swift&lt;/a&gt; UI was to create a simple times tables drilling app. I&amp;rsquo;ve met all the requirements, so I&amp;rsquo;ll move on, but I am struck by how ugly it is. Making better looking apps needs to be added to my goals. Especially since this app is intended to appeal to children, and is at the end of a few lessons on animation, this is definitely a weakness of mine at the moment.&lt;/p&gt;</description></item><item><title>Gitting Xcode to Push</title><link>https://devendevour.iankulin.com/gitting-xcode-to-push/</link><pubDate>Fri, 30 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/gitting-xcode-to-push/</guid><description>&lt;p&gt;I&amp;rsquo;m very comfortable with doing all the routine git stuff from the command line, but it was bugging me that I hadn&amp;rsquo;t for the Xcode integration working. I was able to commit locally with no problem from Xcode, but could not push up to Github. It works fine from the command line, so the error about the change to a stronger SSH authentication didn&amp;rsquo;t really make sense to me.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-26-at-6.57.35-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;</description></item><item><title>Calculator</title><link>https://devendevour.iankulin.com/calculator/</link><pubDate>Thu, 29 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/calculator/</guid><description>&lt;p&gt;The app I&amp;rsquo;m working on currently (for multiplication tables practice) has a number type keypad and display a bit like a calculator - but for entering the answers. It&amp;rsquo;s been quite fun to think through all the little problems to make it work how you&amp;rsquo;d expect, so I was quite interested to watch an iOS Academy video where Afraz Siddiqui builds a partially finished SwiftUI version of the iOS Calculator app.&lt;/p&gt;</description></item><item><title>User Defaults &amp;amp; Horizontal Pickers</title><link>https://devendevour.iankulin.com/user-defaults-horizontal-pickers/</link><pubDate>Wed, 28 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/user-defaults-horizontal-pickers/</guid><description>&lt;p&gt;I&amp;rsquo;m on the challenges for &lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/3/3/challenge" target="_blank" rel="noopener"&gt;Day 35&lt;/a&gt; of &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;100 Days of SwiftUI&lt;/a&gt; , and despite Paul&amp;rsquo;s very clear warning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Important:&lt;/strong&gt; It’s really easy to get sucked into these challenges and spend hours&lt;/em&gt;&amp;hellip;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have spent ages fiddling around, but of course still learning. My issue is not so much getting stuck on bugs, rather I keep wanting to do things I don&amp;rsquo;t know how to do.&lt;/p&gt;
&lt;p&gt;One issue was solved for my by the wonderful &lt;a href="https://firesideswift.fireside.fm/" target="_blank" rel="noopener"&gt;Fireside Swift&lt;/a&gt; podcast. I&amp;rsquo;m working through the old (Steve &amp;amp; Zac) episodes, and they did one on the UserDefaults just when I wanted to be able to persist the multiplication table selection the user had made (this challenge app is a multiplication tables drill app for kids).&lt;/p&gt;</description></item><item><title>Gitting up to date</title><link>https://devendevour.iankulin.com/gitting-up-to-date/</link><pubDate>Tue, 27 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/gitting-up-to-date/</guid><description>&lt;p&gt;I&amp;rsquo;ve started the habit of branching my code for each feature or batches of features. This is not really needed, I&amp;rsquo;ve developing solo, and the code on &lt;code&gt;main&lt;/code&gt; is not in production. I could just go on committing, but part of my process is about becoming competent with git.&lt;/p&gt;
&lt;p&gt;There are a couple of git commands (&lt;code&gt;merge&lt;/code&gt; and &lt;code&gt;rebase&lt;/code&gt;) that mush code between branches together in different ways. The video below (from &lt;a href="https://www.udemy.com/user/manuel-lorenz/" target="_blank" rel="noopener"&gt;Manuel Lorenz&lt;/a&gt; at &lt;a href="https://academind.com/" target="_blank" rel="noopener"&gt;Academind&lt;/a&gt; ) is a particularly clear look at these two commands.&lt;/p&gt;</description></item><item><title>iOS Academy</title><link>https://devendevour.iankulin.com/ios-academy-2/</link><pubDate>Mon, 26 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ios-academy-2/</guid><description>&lt;p&gt;A YouTube channel worth subscribing to is &lt;a href="https://www.linkedin.com/in/afrazsiddiqui" target="_blank" rel="noopener"&gt;Afraz Siddiqui&amp;rsquo;s&lt;/a&gt; &lt;a href="https://www.youtube.com/c/iOSAcademy/videos" target="_blank" rel="noopener"&gt;iOS Academy&lt;/a&gt; . He does a great videos on iOS development. My favouriets might be the shorter focussed ones, like this one on the new SwiftUI chart views.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/KVz_I10R-wA?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;</description></item><item><title>Ranges</title><link>https://devendevour.iankulin.com/ranges/</link><pubDate>Sun, 25 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ranges/</guid><description>&lt;p&gt;I wondered aloud, in a &lt;a href="https://devendevour.iankulin.com/project-4-challenges/"&gt;previous post&lt;/a&gt; , about the differences in writing a range as&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; ForEach(1..&amp;lt;21) {
 Text(String($0))
 }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;versus&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; ForEach(1...20) {
 Text(String($0))
 }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And that&amp;rsquo;s been answered in in one of the Day 34 articles. It sounds like older versions of Swift might not have allowed the second version.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/3/2/key-points" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-18-at-6.51.32-pm.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;</description></item><item><title>Animating Guess The Flag</title><link>https://devendevour.iankulin.com/animating-guess-the-flag/</link><pubDate>Sat, 24 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/animating-guess-the-flag/</guid><description>&lt;p&gt;The challenges for &lt;a href="https://www.hackingwithswift.com/100/swiftui/34" target="_blank" rel="noopener"&gt;Project 6&lt;/a&gt; of 100 Days of SwiftUI was to add some animations to the &lt;a href="https://devendevour.iankulin.com/project-2-guess-the-flag/"&gt;Guess the Flag&lt;/a&gt; app from a little while ago. The animations themselves were not particularly tricky, my main issue was that I was creating the views for the three flags in a ForEach, so the animations were applied to all three flags, but we wanted different animations for the flag the user had clicked versus those they had not.&lt;/p&gt;</description></item><item><title>Animation Feedback</title><link>https://devendevour.iankulin.com/animation-feedback/</link><pubDate>Sat, 24 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/animation-feedback/</guid><description>&lt;p&gt;I had a look at Paul&amp;rsquo;s version of the challenge to animate the Guess The Flags app, and like me he&amp;rsquo;d hit on altering the &lt;em&gt;amount&lt;/em&gt; of each animation depending on if it was the clicked on flag, but he&amp;rsquo;d done it with a lot less code - using the ternary operator in each of the modifiers - versus my approach of filling in an Int array for each of the effects and altering them depending on the user selection.&lt;/p&gt;</description></item><item><title>Animations in Views</title><link>https://devendevour.iankulin.com/animations-in-views/</link><pubDate>Fri, 23 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/animations-in-views/</guid><description>&lt;p&gt;It&amp;rsquo;s a very Apple-thinking thing to be learning about making beautiful and intuitive user experiences this early in a programing tutorial as I am with the &lt;a href="https://www.hackingwithswift.com/100/swiftui/32" target="_blank" rel="noopener"&gt;100 Days of Swift UI&lt;/a&gt; series. Here&amp;rsquo;s a quick look at three different ways of doing animation in SwiftUI Views.&lt;/p&gt;
&lt;h4 id="implicit-animation"&gt;Implicit animation&lt;/h4&gt; &lt;p&gt;An &lt;em&gt;implicit&lt;/em&gt; animation in SwiftUI is when you add a .&lt;em&gt;animation&lt;/em&gt;() modifier to a view. It needs to be bound to the value that&amp;rsquo;s changing so the framework knows to animate when that value changes, and the nature of the change.&lt;/p&gt;</description></item><item><title>Gists for embedding code</title><link>https://devendevour.iankulin.com/gists-for-embedding-code/</link><pubDate>Fri, 23 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/gists-for-embedding-code/</guid><description>&lt;p&gt;So, I might have found a slightly better method for sharing code in posts that I complained about the &lt;a href="https://devendevour.iankulin.com/wordpress-code-blocks/"&gt;other day&lt;/a&gt; . GitHub has a thing called &lt;a href="https://gist.github.com/" target="_blank" rel="noopener"&gt;Gists&lt;/a&gt; . It&amp;rsquo;s like a tiny repository you can paste a code snippet into (or upload a source file). Once that&amp;rsquo;s done, you can just paste the URL of the Gist into &lt;a href="https://wordpress.com/support/gist/" target="_blank" rel="noopener"&gt;Wordpress&lt;/a&gt; - it recognises it and does this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ForEach(&lt;span style="color:#ae81ff"&gt;0.&lt;/span&gt;.&amp;lt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;) { number &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// flag was tapped&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; flagTapped(number)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } label: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FlagView(flagOf: countries[number])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .rotation3DEffect(.degrees(flagSpinAmount[number]),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; axis: (x: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, y: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, z: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .opacity(flagOpacity[number])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .scaleEffect(flagScale[number])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .animation(.&lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;, value: flagSpinAmount)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I came across this being used on a &lt;a href="https://blog.rosay.io/create-a-camera-app-with-swiftui-60876fcb9118" target="_blank" rel="noopener"&gt;blog post&lt;/a&gt; (about using the camera in apps) from &lt;a href="https://rosay.io/" target="_blank" rel="noopener"&gt;Gaspard Rosay&lt;/a&gt; .&lt;/p&gt;</description></item><item><title>Don't Use Stupid Project Names</title><link>https://devendevour.iankulin.com/dont-use-stupid-project-names/</link><pubDate>Thu, 22 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/dont-use-stupid-project-names/</guid><description>&lt;p&gt;I&amp;rsquo;m up to &lt;a href="https://www.hackingwithswift.com/100/swiftui/32" target="_blank" rel="noopener"&gt;Day 32&lt;/a&gt; of 100 Days of Swift UI, and although the tutorial is named &amp;ldquo;Project 6&amp;rdquo; it&amp;rsquo;s not really a project that becomes a simple app, it&amp;rsquo;s really just a series of tutorials on animation that I assume the techniques, but none of the code, will be used later in apps.&lt;/p&gt;
&lt;p&gt;I do find there&amp;rsquo;s some value in typing in the code (rather than cutting and pasting, or just passively watching) so I opened up Xcode to follow along. There not being an app name offered, I used &amp;ldquo;Project 6 - Animation&amp;rdquo;. XCode seemed happy enough with that, and created the directory and placeholder, but then refused to build it saying there was seven errors involving the __PACKAGE_NAME macro and a missing }&lt;/p&gt;</description></item><item><title>Recording the Simulator</title><link>https://devendevour.iankulin.com/recording-the-simulator/</link><pubDate>Wed, 21 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/recording-the-simulator/</guid><description>&lt;p&gt;I&amp;rsquo;ve been using Quicktime to screen record the preview or simulator to document my work here and in Github ReadMe&amp;rsquo;s, but thanks to this &lt;a href="https://sarunw.com/posts/record-ios-simulator-video-and-gif-with-xcode/" target="_blank" rel="noopener"&gt;excellent short post&lt;/a&gt; by &lt;a href="https://twitter.com/sarunw" target="_blank" rel="noopener"&gt;Sarun W&lt;/a&gt; , I now know it&amp;rsquo;s possible to do that directly from the simulator, including capturing gestures, and exporting to animated gifs!&lt;/p&gt;</description></item><item><title>Wordpress Code Blocks</title><link>https://devendevour.iankulin.com/wordpress-code-blocks/</link><pubDate>Wed, 21 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/wordpress-code-blocks/</guid><description>&lt;p&gt;Non-iOS post warning :- )&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not really happy with the way I&amp;rsquo;m sharing code in these posts. I started off with the regular Wordpress code blocks:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; func isPossible(word: String) -&amp;gt; Bool {
 var tempWord = rootWord
 for letter in word {
 if let pos = tempWord.firstIndex(of: letter) {
 tempWord.remove(at: pos)
 } else {
 return false
 }
 }
 return true
 }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These seem a bit large to me, but it comes with a font size choice, which I like setting to &amp;ldquo;Tiny&amp;rdquo;:&lt;/p&gt;</description></item><item><title>Changing Xcode Font Size</title><link>https://devendevour.iankulin.com/changing-xcode-font-size/</link><pubDate>Tue, 20 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/changing-xcode-font-size/</guid><description>&lt;p&gt;I&amp;rsquo;ve been changing the Xcode font size by going into the &lt;em&gt;preferences&lt;/em&gt;, &lt;em&gt;themes&lt;/em&gt;, then selecting the different content types and changing the font size.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-18-at-7.04.52-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I guess I could speed that process up by saving some themes with different text sizes, but I&amp;rsquo;ve noticed a few times in tutorial videos that there must have been an easier way. It turns out that Ctrl + &amp;ldquo;+&amp;rdquo; and Ctrl + &amp;ldquo;-&amp;rdquo; increases and decreases the code font size.&lt;/p&gt;</description></item><item><title>Project 5 - Word Scramble</title><link>https://devendevour.iankulin.com/project-5-word-scramble/</link><pubDate>Mon, 19 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/project-5-word-scramble/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-17-at-4.30.14-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Another &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;100 Days of Swift UI&lt;/a&gt; project wrapped up - this time a scrabble like word game. New techniques included saving a large text file in the app bundle and loading it (via a string) into an array on launch of the view. Also a short adventure into UIKit to use a UITextChecker.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/WordScramble/compare/c64c21d..ada15e2" target="_blank" rel="noopener"&gt;Source&lt;/a&gt; .&lt;/p&gt;</description></item><item><title>Word Scramble Feedback</title><link>https://devendevour.iankulin.com/word-scramble-feedback/</link><pubDate>Mon, 19 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/word-scramble-feedback/</guid><description>&lt;p&gt;As is my practice now, after completing the &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/word-scramble-wrap-up" target="_blank" rel="noopener"&gt;challenges for Project 5&lt;/a&gt; , I reviewed Paul&amp;rsquo;s solution (which is only available to subscribers) to see what he&amp;rsquo;d done better so I could learn from it.&lt;/p&gt;
&lt;p&gt;Most of the differences where not of much significance, but there was a couple of things I picked up:&lt;/p&gt;
&lt;p&gt;When the user had pressed the reset button, to empty the array of word guesses from the previous turn, I had&lt;/p&gt;</description></item><item><title>How to upgrade XCode and Swift</title><link>https://devendevour.iankulin.com/how-to-upgrade-xcode-and-swift/</link><pubDate>Sun, 18 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/how-to-upgrade-xcode-and-swift/</guid><description>&lt;p&gt;With the September release of XCode 14 and Swift 5.7 it was time for my first update - I looked in &amp;ldquo;About&amp;rdquo; for an update link but there wasn&amp;rsquo;t one - so if you&amp;rsquo;re as dense as me, the tip is to head to the &lt;a href="https://www.google.com/url?sa=t&amp;amp;rct=j&amp;amp;q=&amp;amp;esrc=s&amp;amp;source=web&amp;amp;cd=&amp;amp;cad=rja&amp;amp;uact=8&amp;amp;ved=2ahUKEwjN8OGn85r6AhXlWHwKHf85DzAQFnoECBUQAQ&amp;amp;url=https%3A%2F%2Fapps.apple.com%2Fus%2Fapp%2Fxcode%2Fid497799835%3Fmt%3D12&amp;amp;usg=AOvVaw2fEvMbfRtGhB4SPHYB54NX" target="_blank" rel="noopener"&gt;Mac App Store&lt;/a&gt; and have a look at the Updates page.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-17-at-8.53.34-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Your current XCode version can, of course be found in the &lt;em&gt;XCode | About&lt;/em&gt; dialogue. Mine was on 13.4.1. There&amp;rsquo;s a couple of ways of finding the Swift version - If you&amp;rsquo;ve got an XCode project open, click on the .xcodeproj in the explorer,and have a look in &lt;em&gt;Build Settings&lt;/em&gt; for &lt;em&gt;Swift Compiler - Language&lt;/em&gt; for the major version.&lt;/p&gt;</description></item><item><title>How to download a file from GitHub</title><link>https://devendevour.iankulin.com/how-to-download-a-file-from-github/</link><pubDate>Sat, 17 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/how-to-download-a-file-from-github/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-13-at-9.12.31-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A quick tip - since it was not immediately obvious to me. If you need to download a file from GitHub (as opposed to cloning it etc), look for the &amp;ldquo;Raw&amp;rdquo; button - that&amp;rsquo;s a link to the file, then right click and do whatever your browser needs to download a web resource.&lt;/p&gt;</description></item><item><title>Project 4 Challenges</title><link>https://devendevour.iankulin.com/project-4-challenges/</link><pubDate>Fri, 16 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/project-4-challenges/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-13-at-7.22.43-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve completed the Project 4 challenges (source) of the 100 Days of SwiftUI, no biggie - the increase in difficulty between each step of Paul&amp;rsquo;s bootcamp is small enough that it&amp;rsquo;s never too stressful, but large enough you feel like you&amp;rsquo;re progressing all the time.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;ve paid to be a member of Hacking with Swift, one of the perks is to see Paul&amp;rsquo;s video solutions. I&amp;rsquo;ve not worries about it before, but I should - looking at them and comparing to my efforts is probably good feedback. So here&amp;rsquo;s the differences in our answers to the challenges.&lt;/p&gt;</description></item><item><title>Machine Learning</title><link>https://devendevour.iankulin.com/machine-learning/</link><pubDate>Thu, 15 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/machine-learning/</guid><description>&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/coreml" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/rendered2x-1638462885.png" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;A few years ago when I still used a Tom-Tom for car navigation, I was a little freaked out when it started offering suggestions on where to go to when I started the car - guessing, usually correctly, where I wanted to go. Like - how did it know I was leaving school for band practice two towns over?&lt;/p&gt;
&lt;p&gt;Clearly, is must have been collecting data on times/days and departure locations to learn some of my habits. It felt quite invasive, but I thought it must have been on-device since I had the wifi turned off in the unit.&lt;/p&gt;</description></item><item><title>Before SwiftUI</title><link>https://devendevour.iankulin.com/before-swiftui/</link><pubDate>Wed, 14 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/before-swiftui/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-10-at-9.28.24-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m on Day 26 of 100 Days, and didn&amp;rsquo;t grok the dates on my first read through, so I&amp;rsquo;ve read a couple of other explanations and sat down with a coffee and thought I&amp;rsquo;d see what YouTube had for me on the subject. I seen a few great &lt;a href="https://www.youtube.com/c/iOSAcademy/videos" target="_blank" rel="noopener"&gt;iOS Academy&lt;/a&gt; videos, so &lt;a href="https://www.youtube.com/watch?v=HSFTzcYzuEQ" target="_blank" rel="noopener"&gt;this one&lt;/a&gt; seemed like a good choice.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t seen enough to say if it is a good or great explanation of dates, calendars and date components in Swift yet, but man, getting to the stage of writing useful code when using storyboards and UIKit takes a while! It&amp;rsquo;s literally 3:42 in to the video before there&amp;rsquo;s enough infrastructure done for &amp;ldquo;hello world&amp;rdquo;.&lt;/p&gt;</description></item><item><title>Rock, Paper Scissors (2)</title><link>https://devendevour.iankulin.com/rock-paper-scissors-2/</link><pubDate>Tue, 13 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/rock-paper-scissors-2/</guid><description>&lt;p&gt;When I was forced by a deadline into delivering this project, I noted in its &lt;a href="https://devendevour.iankulin.com/rock-paper-scissors-1/"&gt;post&lt;/a&gt; that there was a number of improvements to make:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;The rock paper scissors could be some better data structure than an array and some ints.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;I don’t love the try to win, try to lose aspect, but the client specified it&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Having the didUserWin and didComputerWin funcs is a cop out – that should probably be a single function returning a win/lose/draw type&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;I also am unhappy with nesting them in the view namespace to use my #consts&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;duplicating the last part of the view but making the elements .hidden() to keep the same spacing seems like a kludge&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;when I added the link to the Hacking With SwiftUI page with the app brief just now, I noticed I haven’t done the scoring the way it was asked for&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&amp;rsquo;s the progress&lt;/p&gt;</description></item><item><title>Swift enums</title><link>https://devendevour.iankulin.com/swift-enums/</link><pubDate>Mon, 12 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/swift-enums/</guid><description>&lt;p&gt;I&amp;rsquo;ve started on the refactoring for &lt;a href="https://devendevour.iankulin.com/rock-paper-scissors-1/"&gt;Rock, paper, scissors&lt;/a&gt; . One of the things I didn&amp;rsquo;t like was using Ints to signal which shape (I&amp;rsquo;m calling the rock, or paper, or scissors hand shape a &lt;em&gt;shape&lt;/em&gt;) was being handed around. The Int I was using was also the index into an array of the emoji&amp;rsquo;s - so if I did an off-by-one I was risking an out of bounds on the array.&lt;/p&gt;</description></item><item><title>.git stuffed</title><link>https://devendevour.iankulin.com/git-stuffed/</link><pubDate>Sun, 11 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/git-stuffed/</guid><description>&lt;p&gt;I&amp;rsquo;m in a bit of a swing with my git process. I usually develop locally committing as needed, then when I reach some sort of first milestone, create an empty repo on GitHub the push up to it by:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git remote add origin git@github.com:IanKulin/RockPaper.git
git branch -M main
git push -u origin main
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;or, I start on GitHub, create a new repo with a readme.md in it, and then use the -f (force) flag when I push to it and override the contents. I think forgetting about this might have been the source of tonight&amp;rsquo;s problems with &amp;ldquo;unrelated histories&amp;rdquo;.&lt;/p&gt;</description></item><item><title>Rock, Paper, Scissors (1)</title><link>https://devendevour.iankulin.com/rock-paper-scissors-1/</link><pubDate>Sat, 10 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/rock-paper-scissors-1/</guid><description>&lt;p&gt;As I mentioned yesterday, I needed to make some progress to blog about, and I had a half working version of a Rock, Paper, Scissors for &lt;a href="https://www.hackingwithswift.com/100/swiftui/25" target="_blank" rel="noopener"&gt;Day 25&lt;/a&gt; so I pushed myself to get that working.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s lots in the code below I don&amp;rsquo;t love.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The rock paper scissors could be some better data structure than an array and some ints.&lt;/li&gt;
&lt;li&gt;I don&amp;rsquo;t love the try to win, try to lose aspect, but the client specified it&lt;/li&gt;
&lt;li&gt;Having the didUserWin and didComputerWin funcs is a cop out - that should probably be a single function returning a win/lose/draw type&lt;/li&gt;
&lt;li&gt;I also am unhappy with nesting them in the view namespace to use my #consts&lt;/li&gt;
&lt;li&gt;duplicating the last part of the view but making the elements .hidden() to keep the same spacing seems like a kludge&lt;/li&gt;
&lt;li&gt;when I added the link to the Hacking With SwiftUI page with the app brief just now, I noticed I haven&amp;rsquo;t done the scoring the way it was asked for&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/RockPaper/commit/b1497cccf2dc8af953b946458af797dc5ad12dc9?diff=unified#diff-223dd39ecc4f631b084c99b065a71ea40dc2deba8e36e7f5f939802e60c80186" target="_blank" rel="noopener"&gt;source on github&lt;/a&gt; &lt;/p&gt;</description></item><item><title>A deadline is a good thing</title><link>https://devendevour.iankulin.com/a-deadline-is-a-good-thing/</link><pubDate>Fri, 09 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/a-deadline-is-a-good-thing/</guid><description>&lt;p&gt;I usually have a few days of blog posts written in advance so I can schedule one to come out each day, and not sweat if I&amp;rsquo;m caught up in real life. There&amp;rsquo;s no real reason why I should have that strict publishing schedule, but it is part of my internal discipline to ensure that, at least on average I&amp;rsquo;m making some sort of report-able progress or effort each day.&lt;/p&gt;</description></item><item><title>Learn to Code</title><link>https://devendevour.iankulin.com/learn-to-code/</link><pubDate>Thu, 08 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/learn-to-code/</guid><description>&lt;p&gt;This blog exists for a couple of reasons - firstly Paul Hudson insisted on posting progress in the 100 days of SwiftUI on social media, and secondly, when I try to explain something, I&amp;rsquo;m forced to understand it clearly - so I know this is a good learning technique.&lt;/p&gt;
&lt;p&gt;This video from &lt;a href="https://fireship.io/" target="_blank" rel="noopener"&gt;Fireship&lt;/a&gt; says this idea is called the &lt;a href="https://www.lifehack.org/862931/feynman-technique" target="_blank" rel="noopener"&gt;Feynman Technique&lt;/a&gt; &lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/NtfbWkxJTHw?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;</description></item><item><title>.self in ForEach</title><link>https://devendevour.iankulin.com/self-in-foreach/</link><pubDate>Wed, 07 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/self-in-foreach/</guid><description>&lt;p&gt;I&amp;rsquo;m on Day 25 of Hacking With SwiftUI, and &lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/2/2/key-points" target="_blank" rel="noopener"&gt;Paul is making a point&lt;/a&gt; about how SwiftUI can loop over an array to build a view. He starts with this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;let agents = [&amp;#34;Cyril&amp;#34;, &amp;#34;Lana&amp;#34;, &amp;#34;Pam&amp;#34;, &amp;#34;Sterling&amp;#34;]
VStack {
 ForEach(0..&amp;lt;agents.count) {
 Text(agents[$0])
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But then proposes an alternative:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;let agents = [&amp;#34;Cyril&amp;#34;, &amp;#34;Lana&amp;#34;, &amp;#34;Pam&amp;#34;, &amp;#34;Sterling&amp;#34;]
VStack {
 ForEach(agents, id: \.self) {
 Text($0)
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;He explains the use of \.self here by saying&lt;/p&gt;</description></item><item><title>I git it*</title><link>https://devendevour.iankulin.com/i-git-it/</link><pubDate>Tue, 06 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/i-git-it/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/6y8jpj5f4el91.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;This meme&amp;rsquo;s been trending in the &lt;a href="https://old.reddit.com/r/ProgrammerHumor/comments/x3udcz/is_it_really_that_much_easy_because_in_the/" target="_blank" rel="noopener"&gt;r/ProgrammerHumor subreddit&lt;/a&gt; , and although &amp;ldquo;to do literally anything&amp;rdquo; is a stretch, my git / github workflow is pretty routine now using the &lt;a href="https://xkcd.com/1597/" target="_blank" rel="noopener"&gt;relevant xkcd&lt;/a&gt; method, but actually with quite a bit of understanding from the first half of the excellent &lt;a href="https://git-scm.com/book/en/v2" target="_blank" rel="noopener"&gt;Pro Git book&lt;/a&gt; . I highly recommend it.&lt;/p&gt;
&lt;p&gt;I had in my &lt;a href="https://devendevour.wordpress.com/goals/" target="_blank" rel="noopener"&gt;goals&lt;/a&gt; to set up XCode for push (I think I probably just need to generate a token on GitHub and save it in xcode), so I will do that for completion, but I&amp;rsquo;m also enjoying my &lt;a href="https://devendevour.iankulin.com/oh-my-zsh/"&gt;pimped out terminal&lt;/a&gt; so I&amp;rsquo;m pretty much a git cli guy now.&lt;/p&gt;</description></item><item><title>Project 3</title><link>https://devendevour.iankulin.com/project-3/</link><pubDate>Mon, 05 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/project-3/</guid><description>&lt;p&gt;This one&amp;rsquo;s not really a project, just a couple of little updates to earlier work, and a code snippet.&lt;/p&gt;
&lt;h4 id="challenge-1"&gt;Challenge 1&lt;/h4&gt; &lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Go back to project 1 and use a conditional modifier to change the total amount text view to red if the user selects a 0% tip.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first one is pretty simple - a ternary condition to make the total red if the tip is set to zero.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-09-02-at-5.10.56-pm.png" alt="" class="img-responsive"&gt; &lt;/p&gt;</description></item><item><title>Sean != Erica</title><link>https://devendevour.iankulin.com/sean-erica/</link><pubDate>Sun, 04 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/sean-erica/</guid><description>&lt;p&gt;When Swift was newer, there was a bunch of podcasts about it - in early episodes of &lt;a href="https://podcasts.apple.com/au/podcast/fireside-swift/id1269435221" target="_blank" rel="noopener"&gt;Fireside Swift&lt;/a&gt; the existence of a Swift Podcast Network is often mentioned, but now it&amp;rsquo;s more of an established language there&amp;rsquo;s a bit less current content to listen to, and what there is, is less focused on learning Swift and more about what&amp;rsquo;s happening in the community.&lt;/p&gt;
&lt;p&gt;Being firmly in the camp of needing to learn more about the language, I&amp;rsquo;ve listen to a number of older podcasts, or even current ones (such as Fireside) but their older episodes. It is sort of an odd experience traveling on several slightly out of sync timelines, but quite a joy to see what happens to predictions - like the occasion when &lt;a href="https://twitter.com/twostraws" target="_blank" rel="noopener"&gt;Paul Hudson&lt;/a&gt; predicts that an &amp;ldquo;Xcode lite&amp;rdquo; on iPad is unlikely to be able to write apps until a more swift like framework for developing interfaces exists.&lt;/p&gt;</description></item><item><title>Day 23 - Views and Modifiers - Part 4</title><link>https://devendevour.iankulin.com/day-23-views-and-modifiers-part-4/</link><pubDate>Sat, 03 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/day-23-views-and-modifiers-part-4/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/psm_v10_d562_the_hindoo_earth-3.jpg" alt="This image has an empty alt attribute; its file name is psm_v10_d562_the_hindoo_earth-3.jpg" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Then the last trick for for decomposing the views, is to remember we can pass values when we init a struct. So something like this:&lt;/p&gt;
&lt;p&gt;struct ContentView: View {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var body: some View {
 VStack{
 GreenPaddedText(text: &amp;quot;Hello&amp;quot;)
 GreenPaddedText(text: &amp;quot;world&amp;quot;)
 }
}


struct GreenPaddedText: View {
 var text: String

 var body: some View {
 Text(text)
 .foregroundColor(.green)
 .padding()
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;</description></item><item><title>Day 23 - Views and Modifiers - Part 3</title><link>https://devendevour.iankulin.com/day-23-views-and-modifiers-part-3/</link><pubDate>Fri, 02 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/day-23-views-and-modifiers-part-3/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/psm_v10_d562_the_hindoo_earth-3.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The next part of day 23 started to make my brain hurt a bit. It&amp;rsquo;s easy to imagine that when presenting a complex screen - perhaps some data from a source as a mixture of images and text loaded from a database into a scroll-able view, that the view may start to get complex. Then it becomes good practice to decompose the views to make the code clearer, less error prone, and to avoid any unnecessary repetition.&lt;/p&gt;</description></item><item><title>Day 23 - Views and Modifiers - Part 2</title><link>https://devendevour.iankulin.com/day-23-views-and-modifiers-part-2/</link><pubDate>Thu, 01 Sep 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/day-23-views-and-modifiers-part-2/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/psm_v10_d562_the_hindoo_earth-2.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Although &amp;ldquo;immutable&amp;rdquo; the view structs can contain some control statements such as if/then and for loops. So this is quite legal, and useful.&lt;/p&gt;
&lt;p&gt;struct ContentView: View {
@State private var likesGreen = true&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var body: some View {
 if likesGreen {
 Text(&amp;quot;Hello World&amp;quot;)
 .background(.green)
 }
 else
 {
 Text(&amp;quot;Hello World&amp;quot;)
 .background(.blue)
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;But Paul cautions against this, saying:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/conditional-modifiers" target="_blank" rel="noopener"&gt;You can often use regular if conditions to return different views based on some state, but this actually creates more work for SwiftUI – rather than seeing one Button being used with different colors, it now sees two different Button views, and when we flip the Boolean condition it will destroy one to create the other rather than just recolor what it has.&lt;/a&gt; &lt;/p&gt;</description></item><item><title>Day 23 - Views and Modifiers - Part 1</title><link>https://devendevour.iankulin.com/day-23-views-and-modifiers-part-1/</link><pubDate>Wed, 31 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/day-23-views-and-modifiers-part-1/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/psm_v10_d562_the_hindoo_earth-1.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I found this one of the trickier days, so I&amp;rsquo;ll write it out to clear up my thinking.&lt;/p&gt;
&lt;p&gt;To draw to to screen in SwiftUI, we don&amp;rsquo;t call a command to draw on a canvas or window. Rather, a &lt;em&gt;view&lt;/em&gt; is defined as an immutable struct of type some View. Here&amp;rsquo;s the simple one from the default Xcode project.&lt;/p&gt;
&lt;p&gt;struct ContentView: View {
var body: some View {
Text(&amp;ldquo;Hello, world!&amp;rdquo;)
.padding()
}
}&lt;/p&gt;</description></item><item><title>Project 2 - Guess the Flag</title><link>https://devendevour.iankulin.com/project-2-guess-the-flag/</link><pubDate>Wed, 31 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/project-2-guess-the-flag/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-08-23-at-8.26.59-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Another 100 Days project - the second tutorial one. This was once again a &amp;ldquo;V&amp;rdquo; design pattern (put everything in the view) and as I kept growing it, especially in the challenges, I had a growing sense of unease.&lt;/p&gt;
&lt;p&gt;New things for me was how image assets work - identifying them with strings is convenient, but I&amp;rsquo;m hoping there&amp;rsquo;s safer system later using enums or something to avoid runtime surprises. Also the alert dialog box system. I was wondering how this was going to work in a declarative framework. I do not really approve of modal dialogs in mobile UI&amp;rsquo;s but I guess they have their place. I appreciated the gradients and frosted glass effects -super simple to implement, and if done thoughtfully, pretty.&lt;/p&gt;</description></item><item><title>Download a Directory from a GitHub Repo</title><link>https://devendevour.iankulin.com/download-a-directory-from-a-github-repo/</link><pubDate>Tue, 30 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/download-a-directory-from-a-github-repo/</guid><description>&lt;p&gt;For &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/guess-the-flag-introduction" target="_blank" rel="noopener"&gt;Challenge 2&lt;/a&gt; in the 100 days, I needed to download a directory of flag images from Paul&amp;rsquo;s GitHub. He has all the projects as sub-directories of a single &amp;ldquo;Hacking With Swift&amp;rdquo; repo. I didn&amp;rsquo;t need to whole thing, just the directory with the images.&lt;/p&gt;
&lt;p&gt;Strangely, git does not have any simple way of doing this. Neither does GitHub - I assumed the web interface would have a &amp;ldquo;download as zip&amp;rdquo; option as it does for tags.&lt;/p&gt;</description></item><item><title>Challenge 1</title><link>https://devendevour.iankulin.com/challenge-1/</link><pubDate>Mon, 29 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/challenge-1/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-08-22-at-8.54.26-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m up to Challenge 1 of 100 Days of SwiftUI (&lt;a href="https://www.hackingwithswift.com/100/swiftui/19" target="_blank" rel="noopener"&gt;Day 19&lt;/a&gt; ) which is to make your own simple (no MVVM) version of the app built in the previous three days. It&amp;rsquo;s about as simple as can be whilst still feeling like a real app. Something I hadn&amp;rsquo;t done before was limiting the keyboard to numbers or adding a toolbar to close it, so that was nice.&lt;/p&gt;
&lt;p&gt;Something that&amp;rsquo;s not nice, is that when you touch into the text field to change the number, it&amp;rsquo;s not selected ready to type over (the way they always are in browser url fields) so you need to backspace over the previous entry. That&amp;rsquo;s the sort of anoying behaviour I don&amp;rsquo;t like. It seems (after some googling) there&amp;rsquo;s no straightforward way of addressing this in SwiftUI, with the best solution involving importing a package. I will come back to that because it is bugging me.&lt;/p&gt;</description></item><item><title>$==Commitment</title><link>https://devendevour.iankulin.com/commitment/</link><pubDate>Sun, 28 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/commitment/</guid><description>&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-08-21-at-9.47.54-am.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Paul Hudson&amp;rsquo;s 100 Days of Swift UI course is free - the videos are on YouTube, all the reading and tasks are available on his website for free. Although I assumed he made his living from the prodigious number of Swift books he&amp;rsquo;s written, he doesn&amp;rsquo;t push them in the course (except for his free book &lt;a href="https://www.hackingwithswift.com/quick-start/swiftui" target="_blank" rel="noopener"&gt;Swift UI by Example&lt;/a&gt; ).&lt;/p&gt;
&lt;p&gt;He&amp;rsquo;s so unpushy, I didn&amp;rsquo;t realise till a few days ago that you could pay to become a &lt;a href="https://www.hackingwithswift.com/plus" target="_blank" rel="noopener"&gt;&amp;ldquo;subscriber&amp;rdquo; to Hacking with Swift&lt;/a&gt; . Really, he&amp;rsquo;s too nice.&lt;/p&gt;</description></item><item><title>Int.times()</title><link>https://devendevour.iankulin.com/int-times/</link><pubDate>Sat, 27 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/int-times/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/shockwave_coaster_sfot.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;When writing &lt;a href="https://devendevour.iankulin.com/the-_-underscore/"&gt;yesterday&amp;rsquo;s post&lt;/a&gt; about iterating through a range or collection and using the underscore to throw away the item, I had in the back of my mind that there should be a more straightforward way of doing something a number of times.&lt;/p&gt;
&lt;p&gt;Just to re-iterate (lol), here&amp;rsquo;s the issue. If we want to print &amp;ldquo;Here&amp;rsquo;s the thing&amp;rdquo; three times, in Swift the simplest we can do is:&lt;/p&gt;
&lt;p&gt;for _ in 1&amp;hellip;3 {
print(&amp;ldquo;Here&amp;rsquo;s the thing&amp;rdquo;)
}&lt;/p&gt;</description></item><item><title>The _ Underscore</title><link>https://devendevour.iankulin.com/the-_-underscore/</link><pubDate>Fri, 26 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/the-_-underscore/</guid><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/The_Undertaker" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/undertaker_with_fire.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve learned (so far) an underscore can be used for a couple of things in Swift, both of them loosely translating to &amp;ldquo;doesn&amp;rsquo;t really matter&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The first is in a parameter name for a function. Swift has a very cool feature I haven&amp;rsquo;t seen before where an argument can have a different internal and external name. As usual, this will make more sense in code. Imagine this:&lt;/p&gt;
&lt;p&gt;func sumNumbers(firstNumber: Int, secondNumber: Int) -&amp;gt; Int {
return firstNumber + secondNumber
}&lt;/p&gt;</description></item><item><title>Uwrap App</title><link>https://devendevour.iankulin.com/uwrap-app/</link><pubDate>Thu, 25 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/uwrap-app/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_2549.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Part of the &lt;a href="https://twitter.com/twostraws" target="_blank" rel="noopener"&gt;@twostraws&lt;/a&gt; programmatic universe is his Swift learning app, &lt;a href="https://apps.apple.com/us/app/unwrap/id1440611372" target="_blank" rel="noopener"&gt;Unwrap&lt;/a&gt; that I&amp;rsquo;ve included in my learning &lt;a href="https://devendevour.wordpress.com/goals/" target="_blank" rel="noopener"&gt;goals&lt;/a&gt; . It presents little snippets of learning with a 60 second video, and in a written version, then tests the user to check their understanding. It is slightly gamified - you get points for answers, but it&amp;rsquo;s not clear to me how that works beyond the satisfying haptics when your score runs up at the end of a section.&lt;/p&gt;</description></item><item><title>Codewars / reduce</title><link>https://devendevour.iankulin.com/codewars-reduce/</link><pubDate>Wed, 24 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/codewars-reduce/</guid><description>&lt;p&gt;&lt;a href="https://www.codewars.com/" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/1_0plbhkaulwnsx4u2mqyn2w.png" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.codewars.com/" target="_blank" rel="noopener"&gt;codewars.com&lt;/a&gt; is a &amp;ldquo;coding practice&amp;rdquo; website. You chose a language and a skill level, then it offers up a task (or &lt;em&gt;kata&lt;/em&gt;) for you to write a suitable function. The first one it gave me was seemed too hard, so I changed my level to beginner and skipped to the next one. This was my task:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; Given an array of integers, find the one that appears an odd number of times.
 
 There will always be only one integer that appears an odd number of times.
 
 Examples
 [7] should return 7, because it occurs 1 time (which is odd).
 [0] should return 0, because it occurs 1 time (which is odd).
 [1,1,2] should return 2, because it occurs 1 time (which is odd).
 [0,1,0,1,0] should return 0, because it occurs 3 times (which is odd).
 [1,2,2,3,3,3,4,3,3,3,2,2,1] should return 4, because it appears 1 time (which is odd).
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I know there&amp;rsquo;s a cool &amp;ldquo;Set&amp;rdquo; container type in Swift, so my plan was to iterate through the array, then for each number if there&amp;rsquo;s no entry in the set, then add one, but if there is, remove it. That way whatever is left in the set at the end must be in the original array an odd number of times. I was pretty pleased with myself. Here&amp;rsquo;s the code I Playground&amp;rsquo;d up:&lt;/p&gt;</description></item><item><title>Sometimes the Gold is in the Comments</title><link>https://devendevour.iankulin.com/sometimes-the-gold-is-in-the-comments/</link><pubDate>Tue, 23 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/sometimes-the-gold-is-in-the-comments/</guid><description>&lt;p&gt;I&amp;rsquo;m still not 100% clear on @ObservedObject v @StateObject. So when YouTube offered up this video, in which Paul promises during the intro that I&amp;rsquo;ll understand the data bindings by the end, I thought it would be the video for me.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/stSB04C4iS4?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;I guess I should really have twigged that I&amp;rsquo;d never heard of @ObjectBinding, but I pushed on to the 12 minute mark when he imports the &lt;em&gt;Combine&lt;/em&gt; framework. Hang on, what&amp;rsquo;s that?&lt;/p&gt;</description></item><item><title>Playgrounds are good</title><link>https://devendevour.iankulin.com/playgrounds-are-good/</link><pubDate>Mon, 22 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/playgrounds-are-good/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_2778.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A couple of times (&lt;a href="https://devendevour.iankulin.com/protocols/"&gt;Protocols&lt;/a&gt; &amp;amp; &lt;a href="https://devendevour.iankulin.com/named-loops/"&gt;Named Loops&lt;/a&gt; ) in the past few days I&amp;rsquo;ve needed to write and run a couple of tiny C or C++ snippets, and I&amp;rsquo;ve acutely felt the lack of Swift Playgrounds for it. It occurred to me that Playgrounds has been instrumental in my enjoyment of learning Swift - it&amp;rsquo;s just a bit magic to grab the closest device and noodle out an idea or to make sure I&amp;rsquo;ve understood a new concept.&lt;/p&gt;</description></item><item><title>Named Loops</title><link>https://devendevour.iankulin.com/named-loops/</link><pubDate>Sun, 21 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/named-loops/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_2768.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Here’s a neat thing I haven’t seen before. Other languages I’ve worked in haven’t had a neat way to break out of a set of nested loops to a particular loop. It’s not an issue that comes up a lot, but when it has I’ve solved it by creating a continue flag and having that as the first condition of each loop.&lt;/p&gt;
&lt;p&gt;To explain, say if we had these two loops (in C):&lt;/p&gt;</description></item><item><title>Checkpoint 9</title><link>https://devendevour.iankulin.com/checkpoint-9/</link><pubDate>Sat, 20 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/checkpoint-9/</guid><description>&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/-JmAbcISEmY?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;/*
Your challenge is this: write a function that accepts an
optional array of integers, and returns one randomly.
If the array is missing or empty, return a random number
in the range 1 through 100.&lt;/p&gt;
&lt;p&gt;If that sounds easy, it’s because I haven’t explained
the catch yet: I want you to write your function in a
single line of code. No, that doesn’t mean you should
just write lots of code then remove all the line breaks
– you should be able to write this whole thing in one
line of code.&lt;/p&gt;</description></item><item><title>Visual Studio Code</title><link>https://devendevour.iankulin.com/visual-studio-code/</link><pubDate>Fri, 19 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/visual-studio-code/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/visual_studio_code_1.35_icon.svg_.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve noticed &lt;a href="https://code.visualstudio.com/" target="_blank" rel="noopener"&gt;Visual Studio Code&lt;/a&gt; in a few videos, and admired what a clean interface it had, and was impressed how opening a terminal window was automatically in the directory you were working in.&lt;/p&gt;
&lt;p&gt;I had a need to write some html/css, and some C++ in the last couple of days, so that seemed like a great excuse to give it a try. I&amp;rsquo;d have to say my opinion of it has only gone up. Clearly, it is right at home with HTML and CSS - code completion and syntax colouring all working nicely. I followed TechWithTim&amp;rsquo;s suggestion to install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer" target="_blank" rel="noopener"&gt;Live Server extension&lt;/a&gt; - which was a completely painless experience.&lt;/p&gt;</description></item><item><title>CSS Intro</title><link>https://devendevour.iankulin.com/css-intro/</link><pubDate>Thu, 18 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/css-intro/</guid><description>&lt;p&gt;When I wrote my last commercial HTML (in 1996 lol) I&amp;rsquo;m pretty sure there was no CSS. It was the land of textured backgrounds, blinking scrolling text, &amp;ldquo;under construction&amp;rdquo; gifs, and links to &lt;a href="https://en.wikipedia.org/wiki/Gopher_%5c%28protocol%5c%29" target="_blank" rel="noopener"&gt;gopher&lt;/a&gt; URLs were not uncommon. So this is an area I need to update my skills a little just to carry on a coherent conversation in the developer world.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve bumped into a couple of &lt;a href="https://www.techwithtim.net/" target="_blank" rel="noopener"&gt;Tech With Tim&lt;/a&gt; &lt;a href="https://www.youtube.com/c/TechWithTim/videos" target="_blank" rel="noopener"&gt;videos&lt;/a&gt; recently, and I really liked his CSS intro for &amp;ldquo;Non-web developers&amp;rdquo;.&lt;/p&gt;</description></item><item><title>Checkpoint 8</title><link>https://devendevour.iankulin.com/checkpoint-8/</link><pubDate>Wed, 17 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/checkpoint-8/</guid><description>&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Ga800-Qgft4?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;/*
Your challenge is this: make a protocol that describes a
building, adding various properties and methods, then
create two structs, House and Office, that conform to it.&lt;/p&gt;
&lt;p&gt;Your protocol should require the following:
A property storing how many rooms it has.
A property storing the cost as an integer
(e.g. 500,000 for a building costing $500,000.)
A property storing the name of the estate agent
responsible for selling the building.
A method for printing the sales summary of the building,
describing what it is along with its other properties.&lt;/p&gt;</description></item><item><title>Protocols</title><link>https://devendevour.iankulin.com/protocols/</link><pubDate>Tue, 16 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/protocols/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/protocoldroid-swe.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The evolution of structs into class-like things that can hold properties &lt;em&gt;and&lt;/em&gt; methods in Swift raised in my mind &amp;ldquo;what about inheritance?&amp;rdquo; - but no: structs in Swift can not use inheritance.&lt;/p&gt;
&lt;p&gt;Swift classes implement inheritance, but only from one class; there&amp;rsquo;s no multiple inheritance. Protocols neatly address both these concerns to a large extent, but perhaps before we look at how they work, we should have a brief diversion into inheritance in C++.&lt;/p&gt;</description></item><item><title>Checkpoint 7</title><link>https://devendevour.iankulin.com/checkpoint-7/</link><pubDate>Mon, 15 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/checkpoint-7/</guid><description>&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/quick-start/beginners/checkpoint-7" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-08-08-at-8.43.44-pm.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;/*
Your challenge is this: make a class hierarchy
for animals, starting with Animal at the top,
then Dog and Cat as subclasses, then Corgi and
Poodle as subclasses of Dog, and Persian and Lion
as subclasses of Cat.&lt;/p&gt;
&lt;p&gt;But there’s more:
The Animal class should have a legs integer
property that tracks how many legs the animal has.
The Dog class should have a speak() method that
prints a generic dog barking string, but each of
the subclasses should print something slightly
different.
The Cat class should have a matching speak() method,
again with each subclass printing something
different.
The Cat class should have an isTame Boolean property,
provided using an initializer.&lt;/p&gt;</description></item><item><title>Scope Creep</title><link>https://devendevour.iankulin.com/scope-creep/</link><pubDate>Sun, 14 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/scope-creep/</guid><description>&lt;p&gt;&lt;a href="https://www.inc.com/lolly-daskal/7-reasons-why-you-need-to-embrace-procrastination.html" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/getty_492816326_104863.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;In project management, and especially in programming &amp;ldquo;scope creep&amp;rdquo; refers to the common situation where what&amp;rsquo;s required to regard a project as finished keeps growing. Most commonly, in the form of extra features required to be added to an application. I&amp;rsquo;ve especially seen this when clients see early versions of applications and it prompts them to request things that were not in the original specification.&lt;/p&gt;
&lt;p&gt;My iOS development learning journey has been experiencing some of this as well. I started out with only four clear &lt;a href="https://devendevour.wordpress.com/goals/" target="_blank" rel="noopener"&gt;goals&lt;/a&gt; :&lt;/p&gt;</description></item><item><title>@ObservedObject v @StateObject</title><link>https://devendevour.iankulin.com/observedobject-v-stateobject/</link><pubDate>Sat, 13 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/observedobject-v-stateobject/</guid><description>&lt;p&gt;The Youtube algorithm thinks I need to watch more MVVM videos, and it turns out it&amp;rsquo;s probably right. A day or two ago in an &lt;a href="https://devendevour.iankulin.com/simple-mvvm/"&gt;MVVM&lt;/a&gt; post using a super simple example, I stored the view model as a property of the view using the @ObservedObject wrapper, as I created it.&lt;/p&gt;
&lt;p&gt;struct ContentView: View {
@ObservedObject var light = LightViewModel()&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var body: some View {
 VStack{
 Spacer()
 if light.isOn(){
 drawLitBulb
 }
 else{
 Image(systemName: &amp;quot;lightbulb.fill&amp;quot;).font(.system(size: 72))
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But then today, Youtube served me up this video from &lt;a href="https://www.youtube.com/c/BeyondOnesAndZeros/videos" target="_blank" rel="noopener"&gt;BeyondOnesAndZeros&lt;/a&gt; &lt;/p&gt;</description></item><item><title>Rust</title><link>https://devendevour.iankulin.com/rust/</link><pubDate>Fri, 12 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/rust/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/rustmemelovetriangle_297886754.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s been exciting to see some of the modern language features in Swift - it&amp;rsquo;s a real joy to work in when I think back to my C++ days which was some of the last commercial programming I did.&lt;/p&gt;
&lt;p&gt;The buzz about Carbon got me wondering about other new languages and what might be going on with them. Rust seems to keep popping up in conversations so I thought I&amp;rsquo;d have a quick look.&lt;/p&gt;</description></item><item><title>Simple MVVM</title><link>https://devendevour.iankulin.com/simple-mvvm/</link><pubDate>Thu, 11 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/simple-mvvm/</guid><description>&lt;p&gt;MVVM (Model-View-View Model) is an architectural pattern for apps that separates the data (Model) from the user interface (View). The communication between these two parts is facilitated by a View Model.&lt;/p&gt;
&lt;p&gt;Model &amp;lt;-&amp;gt; View Model &amp;lt;-&amp;gt; View&lt;/p&gt;
&lt;h3 id="model"&gt;Model&lt;/h3&gt; &lt;p&gt;The &lt;em&gt;Model&lt;/em&gt; is platform independent - we should be able to pluck it out and add it to a different application running on a different platform without any trouble. Any business rules will be part of the Model along with the data. For example, if it&amp;rsquo;s a rule that every customer has a sales contact, this can be enforced in the Model.&lt;/p&gt;</description></item><item><title>Vim</title><link>https://devendevour.iankulin.com/vim/</link><pubDate>Wed, 10 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/vim/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/1-_bwvjb2jzuuzyxgxm6xwqq.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been working through the &lt;a href="https://missing.csail.mit.edu/" target="_blank" rel="noopener"&gt;Missing Semester&lt;/a&gt; lectures from MIT, and recently completed the lecture about the &lt;a href="https://github.com/vim/vim" target="_blank" rel="noopener"&gt;Vim editor&lt;/a&gt; . Vim is a test editor, called from the command line, and optimised for programming - in the sense that it assumes most of the use of the editor is navigating around a big text file making small changes rather than entering large amount of test.&lt;/p&gt;
&lt;p&gt;It uses simple, short key presses (as opposed to mouse movements or using menus or toolbars) to achieve things. This makes it highly efficient for good typists who know all the commands, and slightly incomprehensible to those who do not. An additional level of complexity is the idea of modes. Vim has several modes, the main ones being:&lt;/p&gt;</description></item><item><title>Firebase</title><link>https://devendevour.iankulin.com/firebase/</link><pubDate>Tue, 09 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/firebase/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-08-06-at-10.01.15-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;In the category of &amp;ldquo;getting ahead of myself&amp;rdquo;, my list of potential future apps to develop includes an entry for a simple accounting system. I guess transactions could live in SQLite, on iCloud for sharing between Mac and mobile devices. Nevertheless, while noodling about it, I thought I should think about it being fully online, and I&amp;rsquo;d heard Google&amp;rsquo;s Firebase (probably referring for specifically to &lt;a href="https://firebase.google.com/products/firestore" target="_blank" rel="noopener"&gt;Firestore&lt;/a&gt; ) mentioned in several iOS developer podcasts and thought I should take a look.&lt;/p&gt;</description></item><item><title>iOS Academy</title><link>https://devendevour.iankulin.com/ios-academy/</link><pubDate>Mon, 08 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ios-academy/</guid><description>&lt;p&gt;I seem to consume a lot of &lt;a href="https://iosacademy.io/" target="_blank" rel="noopener"&gt;iOS Academy&lt;/a&gt; videos with great, short (&amp;lt; 10 minute) explanations of various Swift iOS programming topics - particularly little UI topics, like this one on the Grid View.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/A3jOHj9erQ4?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;I really appreciate the generous content provision in the Swift and iOS development community. Perhaps this is the same for lots of technologies, but for someone who started programming pre-internet, it&amp;rsquo;s a stark difference to how I used to learn - so many magazines, and so many 2&amp;quot; thick books.&lt;/p&gt;</description></item><item><title>MVVM Explained</title><link>https://devendevour.iankulin.com/mvvm-explained/</link><pubDate>Sun, 07 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/mvvm-explained/</guid><description>&lt;p&gt;The first nine minutes of &lt;a href="https://www.youtube.com/watch?v=sLHVxnRS75w" target="_blank" rel="noopener"&gt;this video&lt;/a&gt; from &lt;a href="https://twitter.com/Its_Macco" target="_blank" rel="noopener"&gt;Emmanuel Okwara&lt;/a&gt; finally gave me a clear understanding of the difference between MVC and MVVM.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/sLHVxnRS75w?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;In both MVC and MVVM the data &amp;amp; logic (Model) are separated from the part that the user interacts (View). Usually the View is a screen with controls and so on, but that&amp;rsquo;s not compulsory - for example a voice mail app interface would be all audio and DTMF. The point is that in both, the user interface (view) does not mess directly with the data (model) - it has to go through some sort of gatekeeper.&lt;/p&gt;</description></item><item><title>Oh My Zsh</title><link>https://devendevour.iankulin.com/oh-my-zsh/</link><pubDate>Sat, 06 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/oh-my-zsh/</guid><description>&lt;p&gt;I&amp;rsquo;ve been playing in the zsh shell since I started on the &lt;a href="https://devendevour.iankulin.com/missing-semester/"&gt;Missing Semester&lt;/a&gt; , and was wondering how to get my git branch name in the prompt. A few googles later, I&amp;rsquo;ve installed Oh My Zsh, and added the git and macos plugins. Pretty.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-08-02-at-7.26.08-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;</description></item><item><title>Missing Semester</title><link>https://devendevour.iankulin.com/missing-semester/</link><pubDate>Fri, 05 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/missing-semester/</guid><description>&lt;p&gt;On my git odyssey yesterday, I came across which is an MIT class for CS about practical things CS students don&amp;rsquo;t strictly need for their degree, but will greatly benefit from. I was interested in their git introduction, but they explain &lt;a href="https://missing.csail.mit.edu/" target="_blank" rel="noopener"&gt;the course&lt;/a&gt; by saying:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://missing.csail.mit.edu/" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-08-01-at-8.50.12-pm.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Videos of the lectures, and all the course notes and assignments are freely made available. I&amp;rsquo;ve only watched the first lecture about the shell, and their git lecture. Both were excellent, so I&amp;rsquo;ll add this series to by &lt;a href="https://devendevour.wordpress.com/goals/" target="_blank" rel="noopener"&gt;Goals&lt;/a&gt; .&lt;/p&gt;</description></item><item><title>Gitting the hang of it</title><link>https://devendevour.iankulin.com/gitting-the-hang-of-it/</link><pubDate>Thu, 04 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/gitting-the-hang-of-it/</guid><description>&lt;p&gt;&lt;a href="https://xkcd.com/1597/" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/git_2x.png" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;I spent most of the day learning about, and practicing with git. I&amp;rsquo;ll list some of the resources at the bottom, but for the moment, this is my understandings / cheat sheet for git. Since this could conceivably turn up in someone&amp;rsquo;s google search, and slightly less conceivably be of some use, I will come back and edit it if there&amp;rsquo;s something bad/wrong here. Comments would be great if you think that&amp;rsquo;s the case.&lt;/p&gt;</description></item><item><title>Chris Lattner</title><link>https://devendevour.iankulin.com/chris-lattner/</link><pubDate>Wed, 03 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/chris-lattner/</guid><description>&lt;p&gt;Thank you YouTube algorithm for this recommendation - Chris Lattner, the main author of Swift (amongst other things including LVM) chatting with Lex Fridman. Ignore the clickbait title. There is a good, brief discussion about the tradeoffs in value vs references types which is a topic I&amp;rsquo;ve been thinking a bit about this week.&lt;/p&gt;
&lt;p&gt;Also some interesting comments about how a language delivers it&amp;rsquo;s complexity. Chris gives the funny example of what &amp;ldquo;hello world&amp;rdquo; looks like in Swift vs C++. Here&amp;rsquo;s Swift: &lt;code&gt;Print(&amp;quot;Hello world&amp;quot;)&lt;/code&gt;, here&amp;rsquo;s C++:&lt;/p&gt;</description></item><item><title>Retain Cycle</title><link>https://devendevour.iankulin.com/retain-cycle/</link><pubDate>Tue, 02 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/retain-cycle/</guid><description>&lt;p&gt;Variables and constants in Swift can be a &lt;em&gt;value type&lt;/em&gt; (their data is copied when they are copied) or a &lt;em&gt;reference type&lt;/em&gt; (a pointer to the data is passed when they are copied.&lt;/p&gt;
&lt;p&gt;Structs, integers, and enums are value types, classes are reference types.&lt;/p&gt;
&lt;p&gt;Memory management of value types is relatively straightforward - there’s a 1:1 relationship between the variable name and its data, so if the variable goes out of scope it can get the chop. With reference types, it’s possible to have several variables (or class or struct properties etc) all pointing to the data, so a more sophisticated system is needed to know when it’s safe to delete the data.&lt;/p&gt;</description></item><item><title>Bump One</title><link>https://devendevour.iankulin.com/bump-one/</link><pubDate>Mon, 01 Aug 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/bump-one/</guid><description>&lt;p&gt;Most of the things I’ve learned so far have been familiar, interesting, or cool - but now I’ve ventured far enough into the Swift Language Programming book to find something that is definitely going to take a couple of readings to piece together.&lt;/p&gt;
&lt;p&gt;I was surprised, then pleased with functions as first class types, and the idea of passing closures around is powerful and useful.&lt;/p&gt;
&lt;p&gt;My current difficulty is getting my head around closures capturing variables. It was tolerable (but not safe) when I just thought of it as a pointer, but when turned out the captured variable continues to exist in some sort of zombie state even after the scope where the variable was contained has ended.&lt;/p&gt;</description></item><item><title>Unwrap</title><link>https://devendevour.iankulin.com/unwrap/</link><pubDate>Sun, 31 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/unwrap/</guid><description>&lt;p&gt;Unwrap is the Paul Hudson app for Swift learning. It’s good for using those three minute gaps in life to digest a concept. I’ve incorporated it into my &lt;a href="https://devendevour.wordpress.com/goals/" target="_blank" rel="noopener"&gt;goals&lt;/a&gt; , as some days its the only progress I make.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/fa3cfadd-f6ef-4a05-9131-be5de8f38291.jpeg" alt="" class="img-responsive"&gt; &lt;/p&gt;</description></item><item><title>Xcode Refactor/Rename</title><link>https://devendevour.iankulin.com/xcode-refactor-rename/</link><pubDate>Sat, 30 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/xcode-refactor-rename/</guid><description>&lt;p&gt;This is cool. You can right click on a variable (and I guess any other) name and change it everywhere. No more tedious search and replace.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/SXSQgtGREKw?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;</description></item><item><title>@ScaledMetric</title><link>https://devendevour.iankulin.com/scaledmetric/</link><pubDate>Fri, 29 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/scaledmetric/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-07-23-at-9.04.21-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I solved the problem (well, I googled a &lt;a href="https://stackoverflow.com/questions/72568296/sf-symbol-images-different-sizes" target="_blank" rel="noopener"&gt;stackoverflow result&lt;/a&gt; to the problem) in the previous post about the different heights of the SF Symbols. The answer was to put them in a frame and lock the height. A problem that then arises from that is that when the user changes the text size, they&amp;rsquo;ll be out of wack. Apple&amp;rsquo;s solution to that, introduced in iOS 14 is the &lt;a href="https://developer.apple.com/documentation/swiftui/scaledmetric" target="_blank" rel="noopener"&gt;@ScaledMetric property wrapper&lt;/a&gt; that does some magic I don&amp;rsquo;t fully understand yet.&lt;/p&gt;</description></item><item><title>Memorise Assignment 1</title><link>https://devendevour.iankulin.com/memorise-assignment-1/</link><pubDate>Thu, 28 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/memorise-assignment-1/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-07-23-at-7.33.03-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A small milestone achieved - I&amp;rsquo;ve completed the first assignment from the CS193p lecture series - some minor changes to the app being built in the lectures. There was a couple of things I was unhappy with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The text under the SF Symbols you can see in the preview above not being vertically aligned.&lt;/li&gt;
&lt;li&gt;Having duplicated code in my emoji arrays:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; let animalEmojis = [&amp;#34;🐠&amp;#34;, &amp;#34;🐢&amp;#34;, &amp;#34;🦋&amp;#34;, &amp;#34;🐥&amp;#34;, &amp;#34;🐣&amp;#34;, &amp;#34;🐰&amp;#34;, &amp;#34;🐝&amp;#34;, &amp;#34;🦄&amp;#34;, &amp;#34;🐵&amp;#34;, &amp;#34;🐛&amp;#34;]
 let weatherEmojis = [&amp;#34;🌪&amp;#34;, &amp;#34;🌝&amp;#34;, &amp;#34;🌈&amp;#34;, &amp;#34;🔥&amp;#34;, &amp;#34;🌧&amp;#34;, &amp;#34;🌙&amp;#34;, &amp;#34;🌬&amp;#34;, &amp;#34;☃️&amp;#34;, &amp;#34;☔️&amp;#34;, &amp;#34;🌫&amp;#34;]
 let transportEmojis = [&amp;#34;🚗&amp;#34;, &amp;#34;🚕&amp;#34;, &amp;#34;🚲&amp;#34;, &amp;#34;🚚&amp;#34;, &amp;#34;🛵&amp;#34;, &amp;#34;🚜&amp;#34;, &amp;#34;🛴&amp;#34;, &amp;#34;🛺&amp;#34;, &amp;#34;🚃&amp;#34;, &amp;#34;🚡&amp;#34;]

 // I&amp;#39;m not happy with this duplication //TODO
 @State var emojis = [&amp;#34;🐠&amp;#34;, &amp;#34;🐢&amp;#34;, &amp;#34;🦋&amp;#34;, &amp;#34;🐥&amp;#34;, &amp;#34;🐣&amp;#34;, &amp;#34;🐰&amp;#34;, &amp;#34;🐝&amp;#34;, &amp;#34;🦄&amp;#34;, &amp;#34;🐵&amp;#34;, &amp;#34;🐛&amp;#34;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This second problem is because I couldn&amp;rsquo;t just&lt;/p&gt;</description></item><item><title>SwiftUI Essentials</title><link>https://devendevour.iankulin.com/swiftui-essentials/</link><pubDate>Wed, 27 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/swiftui-essentials/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-07-23-at-4.12.38-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I hadn&amp;rsquo;t fully gotten my head around what&amp;rsquo;s going on with the declarative nature of SwiftUI, until I&amp;rsquo;d watched this video&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s from the &lt;a href="https://developer.apple.com/videos/play/wwdc2019/216/" target="_blank" rel="noopener"&gt;2019 WWDC&lt;/a&gt; which is when (I guess) SwiftUI was new. I still don&amp;rsquo;t have a good handle on how the views are bound to their data, but there is a video from this same series about Data Flows which I imagine will also answer those questions.&lt;/p&gt;</description></item><item><title>iOS Dev Weekly</title><link>https://devendevour.iankulin.com/ios-dev-weekly/</link><pubDate>Tue, 26 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ios-dev-weekly/</guid><description>&lt;p&gt;Dave Verwer&amp;rsquo;s &lt;a href="https://iosdevweekly.com/" target="_blank" rel="noopener"&gt;iOS Dev Weekly&lt;/a&gt; digest of links mainly about Swift libraries was mentioned on a podcast I was listening to last night - perhaps the &lt;em&gt;Swift with Sundell&lt;/em&gt; &lt;a href="https://www.swiftbysundell.com/podcast/16/" target="_blank" rel="noopener"&gt;chat with Sommer Panage&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://iosdevweekly.com/issues/568?#start" target="_blank" rel="noopener"&gt;first issue&lt;/a&gt; (it&amp;rsquo;s an email newsletter) arrived, and it&amp;rsquo;s pretty great. Not too long, chatty but on topic, and with links to follow for more info. As well as new or improved libraries, other topics are mentioned - I went down a rabbit hole on &lt;a href="https://useyourloaf.com/blog/swiftui-split-view-configuration/" target="_blank" rel="noopener"&gt;SwiftUI Split View Configuration&lt;/a&gt; , ending up at this WWDC video about it.&lt;/p&gt;</description></item><item><title>Closures</title><link>https://devendevour.iankulin.com/closures/</link><pubDate>Mon, 25 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/closures/</guid><description>&lt;p&gt;I had one of those synchronicity in learning moments this morning. I am reading &lt;a href="https://docs.swift.org/swift-book/" target="_blank" rel="noopener"&gt;The Swift Book&lt;/a&gt; - ie &lt;em&gt;The Swift Programming Language, Swift 5.7&lt;/em&gt; as part of my cs193p homework, and this morning, in a coffee shop was admiring what a clear, well written explanation was given for &lt;a href="https://docs.swift.org/swift-book/LanguageGuide/Closures.html" target="_blank" rel="noopener"&gt;closures&lt;/a&gt; . It is super well written, stepping the reader through in logical (and digestible) steps.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve never carelessly passed around a pointer to a function and caused the Blue Screen of Death, or done much multi-threaded programming, the use-case for closures, and use of them is going to be challenging at first. Then Swift&amp;rsquo;s ability to cut the syntax down to very little will be challenging.&lt;/p&gt;</description></item><item><title>Xcode Tour</title><link>https://devendevour.iankulin.com/xcode-tour/</link><pubDate>Sun, 24 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/xcode-tour/</guid><description>&lt;p&gt;If you need a solid tour of the basics plus of Xcode, this is a great video from Karin Prater. Its the first video in her &amp;ldquo;Design-oriented course on SwiftUI&amp;rdquo;.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/EDHl1r5vw6Q?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;</description></item><item><title>Checkpoint 6</title><link>https://devendevour.iankulin.com/checkpoint-6/</link><pubDate>Sat, 23 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/checkpoint-6/</guid><description>&lt;pre tabindex="0"&gt;&lt;code&gt;/*
create a struct to store information about a car, including its model, number of seats, and current gear, then add a method to change gears up or down. Have a think about variables and access control: what data should be a variable rather than a constant, and what data should be exposed publicly? Should the gear-changing method validate its input somehow?
*/

struct Car {
 static let maxGear = 10
 static let minGear = 1
 var model = &amp;#34;no model&amp;#34;
 var seats = 4
 private (set) var currentGear = Car.minGear
 
 init (model: String, seats: Int) {
 self.model = model
 self.seats = seats
 }
 
 mutating func gearUp() {
 if currentGear &amp;lt; Car.maxGear{
 currentGear += 1
 }
 }
 
 mutating func gearDown() {
 if currentGear &amp;gt; Car.minGear{
 currentGear -= 1
 }
 }
 
}

var myUte = Car(model: &amp;#34;Rodeo&amp;#34;, seats:2)
print(&amp;#34;My \(myUte.model) has \(myUte.seats) seats and is in gear: \(myUte.currentGear)&amp;#34;)
myUte.gearDown()
print(&amp;#34;My \(myUte.model) has \(myUte.seats) seats and is in gear: \(myUte.currentGear)&amp;#34;)
myUte.gearUp()
print(&amp;#34;My \(myUte.model) has \(myUte.seats) seats and is in gear: \(myUte.currentGear)&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Swift Over Coffee</title><link>https://devendevour.iankulin.com/swift-over-coffee/</link><pubDate>Fri, 22 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/swift-over-coffee/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screenshot-2022-07-17-at-07-44-36-swift-over-coffee-on-apple-podcasts.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;One of the iOS development podcasts in my current rotation is &amp;ldquo;Swift Over Coffee&amp;rdquo;, it&amp;rsquo;s blurb is:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Swift over Coffee is a podcast that helps you keep your Swift skills up to date the easy way, hosted by Paul Hudson and Erica Sadun. Each episode has news, our picks of the week, plus an open ballot where you can share your views on important topics.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And that is about how it goes. In Season One, it&amp;rsquo;s actually Paul and Sean Allen at the mic, they chat about news and topics related to Swift and iOS development, and each week there&amp;rsquo;s a Twitter question that people have chipped in on and the hosts go over these different views in some detail.&lt;/p&gt;</description></item><item><title>SF Symbols</title><link>https://devendevour.iankulin.com/sf-symbols/</link><pubDate>Thu, 21 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/sf-symbols/</guid><description>&lt;p&gt;A couple of times in the App Development seminar I went to, we used system symbols in the place of images, and in his tutorial on Swift UI Basics, Sean Allen spent a few minutes talking about where they come from and how to choose them.&lt;/p&gt;
&lt;p&gt;First, here&amp;rsquo;s how they look in code - this is from the default Hello World app.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;struct ContentView: View {
 var body: some View {
 VStack {
 Image(systemName: &amp;#34;globe&amp;#34;)
 .imageScale(.large)
 .foregroundColor(.accentColor)
 Text(&amp;#34;Hello world&amp;#34;)
 }
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-07-17-at-7.26.23-am.png" alt="" class="img-responsive"&gt; &lt;/p&gt;</description></item><item><title>Passing Data</title><link>https://devendevour.iankulin.com/passing-data/</link><pubDate>Wed, 20 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/passing-data/</guid><description>&lt;p&gt;Sean Allen has come to my notice a couple of times, once where he was mentioned as freelance contractor who is a great contributor to the community (I think perhaps that was on &lt;a href="https://podcasts.apple.com/au/podcast/swiftcoders-interviews-with-swift-developers/id1082937962" target="_blank" rel="noopener"&gt;Swiftcoders Podcast&lt;/a&gt; ), and I&amp;rsquo;ve also bumped into him as co-host (with Paul Hudson) of the early episodes of the &amp;ldquo;&lt;a href="https://podcasts.apple.com/au/podcast/swift-over-coffee/id1435076502" target="_blank" rel="noopener"&gt;Swift over Coffee&lt;/a&gt; &amp;rdquo; podcast.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/HXoVSbwWUIk?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;This video I watched last night is a compilation of the first few videos of &lt;a href="https://seanallen.teachable.com/p/swiftui-fundamentals" target="_blank" rel="noopener"&gt;Sean&amp;rsquo;s SwiftUI course&lt;/a&gt; , and it&amp;rsquo;s pretty great. In particular he does a great job of explaining how to start to refactor child views out and call them, and how all the stacks go together to make a pretty interface. What he does not do is vist/explain any of the Swift language fundamentals. If you don&amp;rsquo;t already know what a struc is, and the Swift flavour of them, it may be a challenging place to start.&lt;/p&gt;</description></item><item><title>App Development in Swift Playgrounds</title><link>https://devendevour.iankulin.com/app-development-in-swift-playgrounds/</link><pubDate>Tue, 19 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/app-development-in-swift-playgrounds/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_1938.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;During the week I attended &amp;ldquo;App Development in Swift Playgrounds&amp;rdquo; run by &lt;a href="https://www.mattrichards.net.au/" target="_blank" rel="noopener"&gt;Matt Richards&lt;/a&gt; with the support of some of the Apple team and hosted by &lt;a href="https://twitter.com/mssgellis" target="_blank" rel="noopener"&gt;Dr Michelle Ellis&lt;/a&gt; . It was aimed a teachers looking at using Playgrounds for digi-tech teaching.&lt;/p&gt;
&lt;p&gt;The day included pulling apart one of the Playgrounds apps and rebuilding it - this being an example of a &amp;ldquo;top-down&amp;rdquo; approach - starting with a complete app and fiddling around with it - to better engage students. The alternative being a bottom-up approach where lesson one would be &amp;ldquo;good morning students, this is a variable, it can hold a value, it has a name we can use to refer to the value&amp;rdquo;.&lt;/p&gt;</description></item><item><title>struct</title><link>https://devendevour.iankulin.com/struct/</link><pubDate>Mon, 18 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/struct/</guid><description>&lt;p&gt;Started on &lt;a href="https://www.hackingwithswift.com/100/swiftui/10" target="_blank" rel="noopener"&gt;Day 10 of 100 days of etc etc&lt;/a&gt; today which is about structs. It was immediately clear when I first started looking at Swift and Swift UI that structs were going to be a big deal. I am used to structs being able to contain a collection of other types, but not methods. So I was confused at why tuples existed; that is now cleared up.&lt;/p&gt;
&lt;p&gt;If structs can have methods as well as properties, it answers the question of why tuples exist, but immediately asks the question, why have classes since structs have all this power? I already know (from my podcast consumption) one of the answers for this is that structs are value types rather than references. When you:&lt;/p&gt;</description></item><item><title>Fireside Swift</title><link>https://devendevour.iankulin.com/fireside-swift/</link><pubDate>Sun, 17 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/fireside-swift/</guid><description>&lt;p&gt;One of the ways I keep engaged in a topic is to listen to podcasts about it. Currently &lt;a href="https://podcasts.apple.com/au/podcast/fireside-swift/id1269435221" target="_blank" rel="noopener"&gt;Fireside Swift&lt;/a&gt; is one of the Swift/SwiftUI/iOS Development podcasts that I have in the rotation.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/firesideswift.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;The blurb for the show is:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Fireside Swift is a popular iOS Development podcast where four buddies discuss a new Swift programming topic each week. They try to stay informal while also conveying the information they know about each topic with bits of humor sprinkled throughout. Have a seat by the fire, and enjoy some nerdy discussion with friends!&amp;rdquo;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Checkpoint 5</title><link>https://devendevour.iankulin.com/checkpoint-5/</link><pubDate>Sat, 16 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/checkpoint-5/</guid><description>&lt;pre tabindex="0"&gt;&lt;code&gt;/*
 Your input is this:
 
 let luckyNumbers = [7, 4, 38, 21, 16, 15, 12, 33, 31, 49]
 
 Your job is to:
 
 Filter out any numbers that are even
 Sort the array in ascending order
 Map them to strings in the format “7 is a lucky number”
 Print the resulting array, one item per line
 
 So, your output should be as follows:
 
 7 is a lucky number
 15 is a lucky number
 21 is a lucky number
 31 is a lucky number
 33 is a lucky number
 49 is a lucky number
 */

let luckyNumbers = [7, 4, 38, 21, 16, 15, 12, 33, 31, 49]

func isNumberOdd(number:Int) -&amp;gt; Bool {
 return number%2 == 1
}

let filteredNumbers = luckyNumbers.filter(isNumberOdd)

// this closure effectively does nothing
let sortedNumbers = filteredNumbers.sorted(by: {$0&amp;lt;$1}) 

let mappedNumbers = sortedNumbers.map({ String($0)+&amp;#34; is a lucky number&amp;#34; })

for i in 0..&amp;lt;mappedNumbers.count {
 print(mappedNumbers[i])
}
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>IanKulin</title><link>https://devendevour.iankulin.com/about/</link><pubDate>Sat, 16 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/about/</guid><description>&lt;p&gt;Ian Bailey, an educator/programmer from Western Australia.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m reviving my software development skills after a break from the industry by learning web development, and iOS development using Swift. Some of my methods for doing that can be found on the &lt;a href="https://devendevour.wordpress.com/goals/" target="_blank" rel="noopener"&gt;Goals&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;In Paul Hudson&amp;rsquo;s &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;100 Days of SwiftUI&lt;/a&gt; course, he stresses the need to post on social media as each day is completed as a way of making yourself accountable. I find it helpful to describe problems, and explain concepts as an aid to clarifying my own understanding, so this blog fulfills that purpose for me.&lt;/p&gt;</description></item><item><title>Create an Empty Folder on GitHub</title><link>https://devendevour.iankulin.com/create-an-empty-folder-on-github/</link><pubDate>Fri, 15 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/create-an-empty-folder-on-github/</guid><description>&lt;p&gt;You can&amp;rsquo;t, but you can create a folder by adding a file through the web interface and using &lt;code&gt;&amp;lt;folder name&amp;gt;/&amp;lt;file name&amp;gt;&lt;/code&gt; - so add a README.md or whatever. Then you can drag your source into the new folder as normal.&lt;/p&gt;
&lt;p&gt;I learned this from Zack West &lt;a href="https://www.alpharithms.com/how-to-create-a-folder-in-github-repos-463022/" target="_blank" rel="noopener"&gt;here&lt;/a&gt; , where there is also a better explanation with pictures.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-07-11-at-11.11.39-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;</description></item><item><title>Minimum Functionality for App Store</title><link>https://devendevour.iankulin.com/minimum-functionality-for-app-store/</link><pubDate>Fri, 15 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/minimum-functionality-for-app-store/</guid><description>&lt;p&gt;In my &lt;a href="https://devendevour.iankulin.com/app-idea/"&gt;post about&lt;/a&gt; my first app, EasterDay, I mentioned that it might be too trivial for an App Store submission. I&amp;rsquo;ve just been reading the App Store Review Guidelines, and there is a section on &lt;a href="https://developer.apple.com/app-store/review/guidelines/#minimum-functionality" target="_blank" rel="noopener"&gt;Minimum Functionality&lt;/a&gt; that seems like it might be too pointless for acceptance.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-07-11-at-9.22.55-am.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll push on and build it anyway as I still need the rest of the learning experience, but may not submit it. Of course I&amp;rsquo;ve got other ideas for apps (like everyone who has ever met an iOS developer) but they are outside my expertise for the time being.&lt;/p&gt;</description></item><item><title>awesome-ios list on GitHub</title><link>https://devendevour.iankulin.com/awesome-ios-list-on-github/</link><pubDate>Thu, 14 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/awesome-ios-list-on-github/</guid><description>&lt;p&gt;I was looking for some more podcasts with Swift fundamentals content when I came across &lt;a href="https://github.com/vsouza/awesome-ios" target="_blank" rel="noopener"&gt;this&lt;/a&gt; great community built awesome list.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/vsouza/awesome-ios" target="_blank" rel="noopener"&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-07-11-at-8.45.25-am.jpg" alt="" class="img-responsive"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a few podcasts on the list I have not come across, so I&amp;rsquo;ll check them out.&lt;/p&gt;</description></item><item><title>Learning Retention</title><link>https://devendevour.iankulin.com/learning-retention/</link><pubDate>Thu, 14 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/learning-retention/</guid><description>&lt;p&gt;In order to have something to put up on GitHub (as part of working all that out) I went back to re-write the Checkpoint 2 code that I&amp;rsquo;d written, but not saved, three or four days ago.&lt;/p&gt;
&lt;p&gt;The task was to count the unique elements in an array. The teaching had been about the complex data types, so clearly the hint was to cast the array to a set. Although the idea of sets is new to me this year, I&amp;rsquo;ve come across them twice. Once in the 100 days course (the same day as having to write this code) and once from a few days earlier from a &lt;a href="https://firesideswift.fireside.fm/157" target="_blank" rel="noopener"&gt;podcast episode&lt;/a&gt; . This is high quality learning - getting the same topic a couple of different ways a few days apart, then having to use the information for real.&lt;/p&gt;</description></item><item><title>Checkpoint 4</title><link>https://devendevour.iankulin.com/checkpoint-4/</link><pubDate>Wed, 13 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/checkpoint-4/</guid><description>&lt;pre tabindex="0"&gt;&lt;code&gt;/*
 The challenge is this: write a function that accepts an integer from 1 through 10,000, and returns the integer square root of that number. That sounds easy, but there are some catches:
 
 You can’t use Swift’s built-in sqrt() function or similar – you need to find the square root yourself.
 If the number is less than 1 or greater than 10,000 you should throw an “out of bounds” error.
 You should only consider integer square roots – don’t worry about the square root of 3 being 1.732, for example.
 If you can’t find the square root, throw a “no root” error.
 */

enum IntSqrtError: Error {
 case low, high, noIntRoot
}

func calculateIntSqrt(_ number:Int) throws -&amp;gt; Int {
 let lowerBound = 1
 let upperBound = 10_000
 if number &amp;lt; lowerBound {throw IntSqrtError.low}
 if number &amp;gt; upperBound {throw IntSqrtError.high}
 // brute force sqrt finder
 for i in lowerBound...number {
 if i*i == number {
 return i
 }
 }
 // none found or we would have returned by now
 throw IntSqrtError.noIntRoot
}

do {
 try print(calculateIntSqrt(5929))
} catch IntSqrtError.low {
 print(&amp;#34;Lower bound error&amp;#34;)
} catch IntSqrtError.high {
 print(&amp;#34;Upper bound error&amp;#34;)
} catch IntSqrtError.noIntRoot {
 print(&amp;#34;No integer root&amp;#34;)
} catch {
 assert(false)
 print(&amp;#34;Unknown error&amp;#34;)
}
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Checkpoint 4 optimisation</title><link>https://devendevour.iankulin.com/checkpoint-4-optimisation/</link><pubDate>Wed, 13 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/checkpoint-4-optimisation/</guid><description>&lt;p&gt;The &lt;a href="https://www.hackingwithswift.com/quick-start/beginners/checkpoint-4" target="_blank" rel="noopener"&gt;Checkpoint 4&lt;/a&gt; task was to find an integer square root of numbers up to 10000. My &lt;a href="https://devendevour.iankulin.com/checkpoint-4/"&gt;first pass solution&lt;/a&gt; was:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;func calculateIntSqrt(_ number:Int) throws -&amp;gt; Int {
 let lowerBound = 1
 let upperBound = 10_000
 if number &amp;lt; lowerBound {throw IntSqrtError.low}
 if number &amp;gt; upperBound {throw IntSqrtError.high}
 // brute force sqrt finder
 for i in lowerBound...number {
 if i*i == number {
 return i
 }
 }
 // none found or we would have returned by now
 throw IntSqrtError.noIntRoot
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Although not coded for speed, there are a couple of subtle optimisations here; the first is that it gives up once it gets to the number instead of going up to the end (it assumes the square root of a number can&amp;rsquo;t be greater than the number), and the second is that it counts up from the bottom rather than down from the top - I&amp;rsquo;m assuming the bottom of the range is richer in square roots than the top.&lt;/p&gt;</description></item><item><title>Gitting Started</title><link>https://devendevour.iankulin.com/gitting-started/</link><pubDate>Wed, 13 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/gitting-started/</guid><description>&lt;p&gt;One of my early goals was to get in the habit of using version control with Git/Github, and I&amp;rsquo;ve got that sorted out today. My source was this excellent, very clear video from &lt;a href="https://www.youtube.com/channel/UCxA99Yr6P_tZF9_BgtMGAWA" target="_blank" rel="noopener"&gt;Gwen Faraday&lt;/a&gt; . I highly recommend it if you are just starting.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/RGOj5yH7evk?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;It possibly helped that I&amp;rsquo;m also on mac, so I didn&amp;rsquo;t have to deal with the &amp;ldquo;or however that&amp;rsquo;s done on your system&amp;rdquo; type problems. Also, where things didn&amp;rsquo;t work as expected, the explanation about what was being done was clear enough that the problem was solvable. For example, the push command Gwen used was:&lt;/p&gt;</description></item><item><title>Tuple Pronunciation</title><link>https://devendevour.iankulin.com/tuple-pronunciation/</link><pubDate>Tue, 12 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/tuple-pronunciation/</guid><description>&lt;p&gt;Another advantage of the videos, that hadn&amp;rsquo;t occurred to me when I &lt;a href="https://devendevour.iankulin.com/cs193p/"&gt;mentioned it the other day&lt;/a&gt; , is learning the correct pronunciation of things you&amp;rsquo;ve only ever read in books.&lt;/p&gt;
&lt;p&gt;Apparently, tuple is pronounced two-pull, and not with the tup to rhyme with cup as I&amp;rsquo;d always imagined. Google has confirmed, so it&amp;rsquo;s not just a UK thing.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screen-shot-2022-07-09-at-12.06.59-pm.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;</description></item><item><title>Learn to Code 1 - Finished</title><link>https://devendevour.iankulin.com/learn-to-code-1-finished/</link><pubDate>Mon, 11 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/learn-to-code-1-finished/</guid><description>&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_2691.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;A little milestone passed - I&amp;rsquo;ve finished the &amp;ldquo;Learn to Code 1&amp;rdquo; Playgrounds book. Next is &amp;ldquo;Learn to Code 2&amp;rdquo;, and I also see there&amp;rsquo;s a few more in the default load, including &amp;ldquo;Get Started with Apps&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_2692.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;Then there&amp;rsquo;s a heap more under &amp;ldquo;More Playgrounds&amp;rdquo; which I guess is like a mini app store for Playgrounds. Some look like complete (but small) apps - such as &amp;ldquo;Meme Creator&amp;rdquo; and &amp;ldquo;Bubble Level&amp;rdquo;. Others are filed under &amp;ldquo;Extend your App&amp;rdquo; and seem to be focused on particular features such as &amp;ldquo;Organizing with Grids&amp;rdquo;.&lt;/p&gt;</description></item><item><title>Instant Errors</title><link>https://devendevour.iankulin.com/instant-errors/</link><pubDate>Sun, 10 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/instant-errors/</guid><description>&lt;p&gt;I&amp;rsquo;m loving how, in XCode and Playgrounds, it&amp;rsquo;s constantly sort of compiling or interpreting in the background so errors are being flagged as you&amp;rsquo;re working. I tried to google the proper name for this but it&amp;rsquo;s clearly so unremarkable as to be un-remarked on. I guess maybe it&amp;rsquo;s a commonplace feature of modern &lt;a href="https://devendevour.iankulin.com/glossary/"&gt;IDEs&lt;/a&gt; , but for someone who literally used to go to make a coffee when compiling a medium size &lt;a href="https://devendevour.iankulin.com/glossary/"&gt;Clipper&lt;/a&gt; , or even years later Visual Studio C++ project, it&amp;rsquo;s a revelation.&lt;/p&gt;</description></item><item><title>Watch or Read</title><link>https://devendevour.iankulin.com/watch-or-read/</link><pubDate>Sun, 10 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/watch-or-read/</guid><description>&lt;p&gt;I&amp;rsquo;m appreciating, in the &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;100 Days of Swift UI&lt;/a&gt; , that &lt;a href="https://twitter.com/twostraws" target="_blank" rel="noopener"&gt;Paul Hudson&lt;/a&gt; has provided a video and a text description for each of the topics. Usually, I&amp;rsquo;ll read the text - a lot of these early topics cover ground well-trodden from my previous experience, so I can skim forwards to find the Swift specific bits I need. However, if I&amp;rsquo;m making up my hour per day of Swift by multitasking it with a meal, then the video is handy.&lt;/p&gt;</description></item><item><title>iPad Pros - Swift Playgrounds</title><link>https://devendevour.iankulin.com/ipad-pros-swift-playgrounds/</link><pubDate>Sat, 09 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/ipad-pros-swift-playgrounds/</guid><description>&lt;p&gt;I&amp;rsquo;m loving Swift Playgrounds - it&amp;rsquo;s getting daily use switching back and forwards between the iPad and MacBook. It&amp;rsquo;s sort of amazing that a tool to support education - it seems designed for primary school students, and is certainly being used that way - scales right up to &amp;ldquo;commercial&amp;rdquo; level app production.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/screenshot-2022-07-07-at-08-01-11-ipad-pros-on-apple-podcasts.png" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="https://ipadpros.net/" target="_blank" rel="noopener"&gt;iPad Pros&lt;/a&gt; is a podcast about iPads (unsurprisingly) by &lt;a href="https://twitter.com/iPadProsPodcast" target="_blank" rel="noopener"&gt;Tim Chaten&lt;/a&gt; and I listened to a &lt;a href="https://podcasts.apple.com/us/podcast/swift-playgrounds-4-with-frank-foster-ipad-pros-0132/id1264565547?i=1000546955634" target="_blank" rel="noopener"&gt;2017 episode about the launch of Playgrounds 4&lt;/a&gt; with guest Frank Foster. The focus was more about using the iPad as a serious development tool - a la XCode for iPad - than the education possibilities. I&amp;rsquo;m all for XCode (or something closer) on iPad, but I&amp;rsquo;d be disappointed if Playgrounds was changed in any way that made it more intimidating for children.&lt;/p&gt;</description></item><item><title>A Couple of Favourite Places</title><link>https://devendevour.iankulin.com/a-couple-of-favourite-places/</link><pubDate>Fri, 08 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/a-couple-of-favourite-places/</guid><description>&lt;p&gt;The Swift documentation is &lt;a href="https://www.swift.org/documentation/" target="_blank" rel="noopener"&gt;here&lt;/a&gt; on the Swift.org website. if you were expecting it to be at &lt;a href="http://Developer.apple.com" target="_blank" rel="noopener"&gt;developer.apple.com&lt;/a&gt; , that’d be fair enough, there is plenty there for iOS developers. Swift however, is it’s own Open Source thing, so it gets its own site. I use the &lt;a href="https://docs.swift.org/swift-book/TheSwiftProgrammingLanguageSwift57.epub" target="_blank" rel="noopener"&gt;epub version&lt;/a&gt; of the &lt;a href="https://docs.swift.org/swift-book/" target="_blank" rel="noopener"&gt;Swift Programming Languag&lt;/a&gt; e book - its just a bit easier to keep my place.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cs193p.sites.stanford.edu/" target="_blank" rel="noopener"&gt;cs193p.sites.stanford.edu&lt;/a&gt; is where I go for the videos and homework assignments for that course.&lt;/p&gt;</description></item><item><title>Checkpoint 3</title><link>https://devendevour.iankulin.com/checkpoint-2/</link><pubDate>Fri, 08 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/checkpoint-2/</guid><description>&lt;pre tabindex="0"&gt;&lt;code&gt; /*
 If it’s a multiple of 3, print “Fizz”
 If it’s a multiple of 5, print “Buzz”
 If it’s a multiple of 3 and 5, print “FizzBuzz”
 Otherwise, just print the number.
 */

for i in 1...100 {

 let isMultOfThree = (i % 3 == 0)
 let isMultOfFive = (i % 5 == 0)

 if (isMultOfFive &amp;amp;&amp;amp; isMultOfThree) {
 print(&amp;#34;FizzBuzz&amp;#34;)
 } else if isMultOfThree {
 print(&amp;#34;Fizz&amp;#34;)
 } else if isMultOfFive {
 print(&amp;#34;Buzz&amp;#34;)
 } else {
 print(i)
 }
}
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>CS193p</title><link>https://devendevour.iankulin.com/cs193p/</link><pubDate>Thu, 07 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/cs193p/</guid><description>&lt;p&gt;I&amp;rsquo;ve loved the first couple of these &amp;ldquo;Getting Started with SwiftUI&amp;rdquo; &lt;a href="https://www.youtube.com/watch?v=bqu6BquVi2M" target="_blank" rel="noopener"&gt;lectures from Paul Hegarty&lt;/a&gt; at Stanford. He&amp;rsquo;s put a lot of thought into the sequence, and seems to address the questions that float up in my mind (with super clear explanations) just as I&amp;rsquo;m thinking of them. They also generously make the reading and homework assignments available at &lt;a href="https://cs193p.sites.stanford.edu/" target="_blank" rel="noopener"&gt;cs193p.sites.stanford.edu&lt;/a&gt; so it&amp;rsquo;s possible to treat it as a course which I have made a bit of a start on, before being distracted by building my own simple app.&lt;/p&gt;</description></item><item><title>Glossary</title><link>https://devendevour.iankulin.com/glossary/</link><pubDate>Thu, 07 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/glossary/</guid><description>&lt;p&gt;Clipper - Was a compiled language to create data-base orientated software for the MS-DOS platform. The database format was compatible with dBase III. I made a small living for a while turning small businessmen&amp;rsquo;s DIY dBase setups into real relational database applications.&lt;/p&gt;
&lt;p&gt;IDE - Integrated Development Environment - the place where you write your code, test, debug etc. XCode is an IDE, vi and a directory of scripts is not.&lt;/p&gt;</description></item><item><title>Playgrounds - Learn to Code</title><link>https://devendevour.iankulin.com/playgrounds-learn-to-code/</link><pubDate>Thu, 07 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/playgrounds-learn-to-code/</guid><description>&lt;p&gt;I mentioned the ”Learn to Code” ”books” that come with Playgrounds (on IPad and Mac) earlier. Here’s a quick example of the sort of challenge:&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/SIm4FrdOZ1M?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;This is towards the end of “Learn to Code 1”, there are ”Learn to code 2”, and ”Get Stared with Apps” books as well.&lt;/p&gt;</description></item><item><title>Playgrounds</title><link>https://devendevour.iankulin.com/playgrounds/</link><pubDate>Wed, 06 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/playgrounds/</guid><description>&lt;p&gt;One of the threads that&amp;rsquo;s led me to learning iOS development is last year&amp;rsquo;s release of the &lt;a href="https://developer.apple.com/swift-playgrounds/" target="_blank" rel="noopener"&gt;Swift Playgrounds&lt;/a&gt; 4 app. I&amp;rsquo;ve long had a hankering for a tool to create IOS apps, and a few years ago invested a bit of time in &lt;a href="https://apps.apple.com/us/app/codea/id439571171" target="_blank" rel="noopener"&gt;Codea&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;Playgrounds possibly started as a little live scratchpad for code in Xcode, but now it&amp;rsquo;s big news in STEM education for getting kids started on coding. It&amp;rsquo;s possible to create (and share) Playground &amp;ldquo;books&amp;rdquo; that lead users through steps in programming. Playgrounds is supplied with one that covers the beginnings of programing - functions, loops, conditions etc and many more are downloadable. This is actually one of the methods I&amp;rsquo;m using for picking up Swift basics - I can pick it up in any spare five minutes and solve a puzzle to progress my learning.&lt;/p&gt;</description></item><item><title>100 days of SwiftUI</title><link>https://devendevour.iankulin.com/100-days-of-swiftui/</link><pubDate>Tue, 05 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/100-days-of-swiftui/</guid><description>&lt;p&gt;Paul Hudson&amp;rsquo;s intro to his &lt;a href="https://www.hackingwithswift.com/100/swiftui" target="_blank" rel="noopener"&gt;IOS development course&lt;/a&gt; asks participants to post on social media each day as part of an effort to keep them motivated. The Insta tag &lt;a href="https://www.instagram.com/explore/tags/100daysofswiftui/?hl=en" target="_blank" rel="noopener"&gt;#100daysofswiftui&lt;/a&gt; is full of curated photos of MacBook Pros showing Xcode 14. This also seems like handy marketing for Paul.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not a crazy idea, so I&amp;rsquo;ll add this to the long list of excitedly started and eventually abandoned blogs of the world. Perhaps a post will even help a forlorn google searcher out on day. I&amp;rsquo;ll aim to keep them short, so it does not become a large enough task to trigger it&amp;rsquo;s own procrastination.&lt;/p&gt;</description></item><item><title>App Idea</title><link>https://devendevour.iankulin.com/app-idea/</link><pubDate>Tue, 05 Jul 2022 00:00:00 +0000</pubDate><guid>https://devendevour.iankulin.com/app-idea/</guid><description>&lt;p&gt;Here&amp;rsquo;s a rough plan for a &amp;ldquo;hello world&amp;rdquo; that&amp;rsquo;s simple enough to be achieved in the short term, but hopefully not too trivial to be accepted into the app store.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://devendevour.iankulin.com/images/img_2680.jpg" alt="" class="img-responsive"&gt; &lt;/p&gt;
&lt;p&gt;This is sketched out on GoodNotes 5. I started with the Apple Notes app, but was frustrated by not being able to mix text and drawing. Noteabilty looked great, but the once off purchase for GoodNotes was easier to swallow.&lt;/p&gt;</description></item></channel></rss>