<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
  <title>David Burke</title>
  <link>https://david.burke.nyc/</link>
  <description>David Burke — software, Django, Kubernetes, and GlitchTip. Notes on technology against you.</description>
  <language>en</language>
  <atom:link href="https://david.burke.nyc/feed.xml" rel="self" type="application/rss+xml"/>
<lastBuildDate>Fri, 23 Jan 2026 12:00:00 +0000</lastBuildDate>  <item>
    <title>Divergent thoughts on AI</title>
    <link>https://david.burke.nyc/blog/divergent-thoughts-on-ai/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/divergent-thoughts-on-ai/</guid>
    <pubDate>Fri, 23 Jan 2026 12:00:00 +0000</pubDate>
<category>ai</category><category>artificial-intelligence</category><category>chatgpt</category><category>llm</category><category>technology</category>    <description>&lt;p&gt;&lt;a href=&#34;https://glitchtip.com&#34;&gt;GlitchTip&lt;/a&gt;, the open source error monitoring tool I started, features no capital-fueled AI language on it&amp;rsquo;s home page. Our LLM wrapper isn&amp;rsquo;t changing your world, it doesn&amp;rsquo;t exist. Compare that to a competitor like Sentry&amp;rsquo;s seer, where the marketing features some fleshy, pained &amp;ldquo;&lt;strong&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/I_Have_No_Mouth,_and_I_Must_Scream&#34;&gt;Allied Mastercomputer&lt;/a&gt;&lt;/strong&gt;&amp;rdquo; or AM watching your errors.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;&#34; src=&#34;https://davidmburkecom.wordpress.com/wp-content/uploads/2026/01/image.png?w=212&#34;&gt;&lt;/p&gt;
&lt;p&gt;Was that intentional to look so pained that it might get attention? I suppose it worked.&lt;/p&gt;
&lt;p&gt;Use the chat widget on the site, and I&amp;rsquo;ll respond slowly when I get around it it. You might think I&amp;rsquo;m a Luddite. I do have a &lt;a href=&#34;https://minimalcompany.com/&#34;&gt;Minimal Phone&lt;/a&gt; now, that I like. It&amp;rsquo;s less distracting. I can&amp;rsquo;t watch video on my phone. Reading slow is fine, but scrolling media is pained. It&amp;rsquo;s much less enticing for my kids to want to grab. It turns internet ads with motion into eink flashing nausea. Which I perversely enjoy hating. It&amp;rsquo;s ugly, but I&amp;rsquo;m glad the ad is rendered incomprehensible.  &lt;/p&gt;
&lt;p&gt;In my day job (Not GlitchTip) I actually do a good bit of ai work. I mean both using ai and implementing it (Building wrappers). Not the fancy building of GPT systems, I understand only at a elementary level, maybe as much as I understand quantum physics.&lt;/p&gt;
&lt;p&gt;In spite of my technology reservations, I actually do like the idea of more tooling to automate coding. I&amp;rsquo;ve always felt drawn more to programming as a way to build something, while not really enjoying writing algorithms very much. I find some coding frustrating. Stop upgrading your packages, I hate your breaking changes.&lt;/p&gt;
&lt;p&gt;Last summer I built this unfinished media visualizer &lt;a href=&#34;https://gitlab.com/bufke/disk-player&#34;&gt;https://gitlab.com/bufke/disk-player&lt;/a&gt; using only ai tools. I just wanted to see if I could. I lack the attention to finish it, but it is kind of cool. I found the experiment successful, I could use my existing knowledge to let a LLM build something for me, only reviewing and guiding it.&lt;/p&gt;
&lt;p&gt;In the fall, I built &lt;a href=&#34;https://gitlab.com/glitchtip/django-vcache&#34;&gt;django-vcache&lt;/a&gt; and &lt;a href=&#34;https://gitlab.com/glitchtip/django-vtasks&#34;&gt;django-vtasks&lt;/a&gt; to replace Celery for me with more asyncio friendly and lighter code. I used a combination of Gemini CLI and Claude+Zed. Working on open source, I can&amp;rsquo;t say I built some amazing $80k project with $200 of credits. All I could say is that I built a $0 money pit with $200 of credits and now I cannot afford coffee. Gemini pro $20/month pricing is reasonable and I paid for Zed pro mainly to support an open source project that I like. You should do this too, maybe for Zed or &lt;a href=&#34;https://en.liberapay.com/GlitchTip&#34;&gt;GlitchTip&lt;/a&gt;. My total budget is then capped $50/month, that&amp;rsquo;s less coffee but not no coffee for me.&lt;/p&gt;
&lt;p&gt;I use a Gemini Pro web conversation as a architect/planner and CLI/Zed as the doer. The web conversation is a playground for ideas and longer historical context. LLM context is kept lower as it&amp;rsquo;s not writing much code. I might have a good idea while on the subway and quickly update the conversation. The Minimal phone does has a nice physical keyboard for quick writing. To execute, I can have the architect LLM write a prompt for CLI/Zed. I can let CLI run while cooking. Then give a deep dive when merging, when I&amp;rsquo;m not distracted. I ended up writing many unit tests myself to ensure correctness, the models tend to write tests for the sake of testing and not proving anything. They are too full of mocks and misunderstanding. The better tests ground changes, avoiding one feature breaking another. Or the totally bonkers things LLM&amp;rsquo;s do sometimes. Early results for these projects are very promising. They perform well and are good for my half-distracted lifestyle. I don&amp;rsquo;t suspect I would consider a &amp;ldquo;make my own Celery&amp;rdquo; type project without them.&lt;/p&gt;
&lt;p&gt;I could imagine using voice controls for these tools. Maybe I talk to gemini while holding my baby. I get work done at all hours of the day, supervising it. Let it work alone at night. During dedicated work time, I review deeply and commit. Sounds like a hell AM might dream up. Total distraction. Total focus on a feeling of work. Unsure if it&amp;rsquo;s useful, effective, dangerous.&lt;/p&gt;
&lt;p&gt;I think I&amp;rsquo;ll avoid voice controls for now.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a better use case. I maintain all of these test projects. &lt;a href=&#34;https://gitlab.com/glitchtip/error-factories&#34;&gt;https://gitlab.com/glitchtip/error-factories&lt;/a&gt;. They ensure the sentry-sdk works with GlitchTip. They auto update themselves with Renovate. They send events to a staging GlitchTip server every day. A LLM manages their maintenance when tests fail. This is a highly effective use of ai in my opinion. As a large language model, I—just kidding. Nothing in error factories is production code. And it&amp;rsquo;s boring code. In this one case, I think the ai usage is firmly more time with family and coffee.&lt;/p&gt;</description>
  </item>
  <item>
    <title>openapi-typescript with Angular Resource</title>
    <link>https://david.burke.nyc/blog/openapi-typescript-with-angular-resource/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/openapi-typescript-with-angular-resource/</guid>
    <pubDate>Wed, 01 Jan 2025 12:00:00 +0000</pubDate>
<category>angular</category><category>openapi</category><category>programming</category><category>typescript</category>    <description>&lt;p&gt;Angular 19 introduces &lt;a href=&#34;https://angular.dev/guide/signals/resource&#34;&gt;Resource&lt;/a&gt;, an async signal, typically used for web requests. I find it reminiscent of TanStack Query but more minimalist and signal based. While RxResource works great with Angular HTTPClient, the regular &amp;ldquo;Resource&amp;rdquo; works with good old fetch. Which means we can combine it with openapi-typescript and &lt;a href=&#34;https://openapi-ts.dev/openapi-fetch/&#34;&gt;openapi-fetch&lt;/a&gt;. The result will let us quickly grab typed API resources and process them with Angular Signals, no rxjs needed.  &lt;/p&gt;
&lt;p&gt;Follow openapi-fetch&amp;rsquo;s &lt;a href=&#34;https://openapi-ts.dev/openapi-fetch/#setup&#34;&gt;documentation&lt;/a&gt; to install and generate your openapi types. I called mine src/app/api/api-schema.d.ts. Then create a file such as src/app/api/api.ts&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;import createClient from &amp;quot;openapi-fetch&amp;quot;;
import type { paths } from &amp;quot;./api-schema&amp;quot;;

export const client = createClient&amp;lt;paths&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s imagine fetching a Foo api in a Service.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;export class FooService {
  foosResource = resource({
    loader: async () =&amp;gt; {
      const { data, error } = await client.GET(&amp;quot;/api/foo/&amp;quot;);
      // Consider error handling here
      return data
  })
  foos = computed(() =&amp;gt; this.foosResource.value() || [])
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Our foos will be typed according to the openapi spec. We can also refer to the types directly:&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;import type { components } from &amp;quot;src/app/api/api-schema&amp;quot;;
type Foo = components[&amp;quot;schemas&amp;quot;][&amp;quot;FooSchema&amp;quot;];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With this approach, we can easily get typed API data into Angular and it looks more like any other JS framework code.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Monitor network endpoints with Python asyncio and aiohttp</title>
    <link>https://david.burke.nyc/blog/monitor-network-endpoints-with-python-asyncio-and-aiohttp/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/monitor-network-endpoints-with-python-asyncio-and-aiohttp/</guid>
    <pubDate>Fri, 06 Aug 2021 12:00:00 +0000</pubDate>
<category>django</category><category>glitchtip</category><category>python</category>    <description>&lt;p&gt;My motivation - I wanted to make a network monitoring service in Python. Python isn&amp;rsquo;t known for it&amp;rsquo;s async ability, but with asyncio it&amp;rsquo;s possible. I wanted to include it in a larger Django app, &lt;a href=&#34;http://glitchtip.com&#34;&gt;GlitchTip&lt;/a&gt;. Keeping everything as a monolithic code base makes it easier to maintain and deploy. Go and Node handle concurrent IO a little more naturally but don&amp;rsquo;t have any web framework even close to as feature complete as Django.&lt;/p&gt;
&lt;h2 id=&#34;how-asyncio-works-compared-to-javascript&#34;&gt;How asyncio works compared to JavaScript&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m used to synchronous Python and asynchronous JavaScript. asyncio is a little strange at first. It&amp;rsquo;s far more verbose than just stringing along a few JS promises. Let&amp;rsquo;s compare this example of JS and Python.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fetch(&amp;#39;http://example.com/example.json&amp;#39;)
  .then(response =&amp;gt; response.json())
  .then(data =&amp;gt; console.log(data));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get(&amp;#39;http://example.com/example.json&amp;#39;) as response:
            html = await response.json()
            print(html)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There&amp;rsquo;s more boilerplate in Python. aiohttp has three chained async calls while fetching JSON in JS requires just two chained promises. Let&amp;rsquo;s break these differences down a bit&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An async call to GET/POST/etc the resource. At this time, we don&amp;rsquo;t have the body of the request. fetch vs sessions.get are about the same here.&lt;/li&gt;
&lt;li&gt;An async call to get the body contents (and perhaps process them in some manner such as converting a JSON payload to a object or dictionary). If we only need say the status code, there is no need to spend time doing this. Both have async text() and json() functions that also work similarly.&lt;/li&gt;
&lt;li&gt;aiohttp has a ClientSession context manager that closes the connection. The only async IO occurs when closing the connection. It&amp;rsquo;s possible to reuse a session for some performance benefit. This is often useful in Python as our async code block will often live nested in synchronous code. Fetch does not have this (as far as I&amp;rsquo;m aware at the time of this writing).&lt;/li&gt;
&lt;li&gt;get_event_loop and run_until_complete allow us to run async functions from a synchronous code function. Python is synchronous by default, so this is necessary. When running Django or Celery or a python script, everything is blocking until explicitly run async. JavaScript on the lets you run async code with 0 boilerplate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One other thing to note is that both Python and JavaScript are single threaded. While you can &amp;ldquo;do multiple things&amp;rdquo; while waiting for IO, you cannot use multiple CPU cores without starting multiple processes, for example by running uwsgi workers. Thus in Python it&amp;rsquo;s called async&lt;strong&gt;io&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Source: &lt;a href=&#34;https://docs.aiohttp.org/en/stable/http_request_lifecycle.html#aiohttp-request-lifecycle&#34;&gt;docs.aiohttp.org&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;network-monitoring-with-aiohttp&#34;&gt;Network Monitoring with aiohttp&lt;/h2&gt;
&lt;p&gt;Network monitoring can easily start as a couple line script or be a very complex, massive service depending on scale. I won&amp;rsquo;t claim that my method is the best, mega-scale method ever, but I think it&amp;rsquo;s quite sufficient for small to medium scale projects. Let&amp;rsquo;s start with requirements&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Must handle 1 million network checks per minute&lt;/li&gt;
&lt;li&gt;Must run at least every 30 seconds (smaller scale this could probably go much shorter)&lt;/li&gt;
&lt;li&gt;Must only run Python and get embedded into a Django code base&lt;/li&gt;
&lt;li&gt;Must not require anything other than a Celery compatible service broker and Django compatible database&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And a few non-functional requirements that I believe will help scale&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Must scale to run from many servers (Celery workers)&lt;/li&gt;
&lt;li&gt;Must batch database writes as efficiently as possible to avoid bottlenecks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&#34;&#34; src=&#34;/static/img/2021/08/image.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;Overview of architecture&lt;/p&gt;
&lt;p&gt;A Celery beat scheduler will run a &amp;ldquo;dispatch_checks&amp;rdquo; task every 30 seconds. Dispatch checks will determine which &amp;ldquo;monitors&amp;rdquo; need checked based on their set interval frequency and last check. It will then batch these in groups and dispatch further parallel celery tasks called &amp;ldquo;perform_checks&amp;rdquo; to actually perform the network check. The perform_checks task will then fetch additional monitor data in one query and asynchronously check each network asset. Once done, it will save to the database using standard Django ORM. By batching inserts, we should be able to improve scalability. It also means we don&amp;rsquo;t need a massive number of celery tasks, which would be unnecessary overhead. In real life, we may only have a few celery works for the &amp;ldquo;small or medium scale&amp;rdquo; so it would waste resources to dispatch 1 million celery tasks. If we batch inserts by 1000 and really have our max target of 1 million monitors, then we would want 1000 celery workers. Another variable is the timeout for each check. Making it lower, means our workers get done faster instead of waiting for the slowest request.&lt;/p&gt;
&lt;p&gt;See the full code on GlitchTip&amp;rsquo;s &lt;a href=&#34;https://gitlab.com/glitchtip/glitchtip-backend/-/tree/master/uptime&#34;&gt;GitLab.&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;celery-tasks&#34;&gt;Celery Tasks&lt;/h3&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;@shared_task
def dispatch_checks():
    now = timezone.now()
    latest_check = Subquery(
        MonitorCheck.objects.filter(monitor_id=OuterRef(&amp;quot;id&amp;quot;))
        .order_by(&amp;quot;-start_check&amp;quot;)
        .values(&amp;quot;start_check&amp;quot;)[:1]
    )
    monitor_ids = (
        Monitor.objects.filter(organization__is_accepting_events=True)
        .annotate(
            last_min_check=ExpressionWrapper(
                now - F(&amp;quot;interval&amp;quot;), output_field=DateTimeField()
            ),
            latest_check=latest_check,
        )
        .filter(latest_check__lte=F(&amp;quot;last_min_check&amp;quot;))
        .values_list(&amp;quot;id&amp;quot;, flat=True)
    )
    batch_size = 1000
    batch_ids = []
    for i, monitor_id in enumerate(monitor_ids.iterator(), 1):
        batch_ids.append(monitor_id)
        if i % batch_size == 0:
            perform_checks.delay(batch_ids, now)
            batch_ids = []
    if len(batch_ids) &amp;gt; 0:
        perform_checks.delay(batch_ids, now)

@shared_task
def perform_checks(monitor_ids: List[int], now=None):
    if now is None:
        now = timezone.now()
    # Convert queryset to raw list[dict] for asyncio operations
    monitors = list(Monitor.objects.filter(pk__in=monitor_ids).values())
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(fetch_all(monitors, loop))
    MonitorCheck.objects.bulk_create(
        [
            MonitorCheck(
                monitor_id=result[&amp;quot;id&amp;quot;],
                is_up=result[&amp;quot;is_up&amp;quot;],
                start_check=now,
                reason=result.get(&amp;quot;reason&amp;quot;, None),
                response_time=result.get(&amp;quot;response_time&amp;quot;, None),
            )
            for result in results
        ]
    )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The fancy Django ORM subquery is to ensure we are able to determine which monitors need checked while being as performant as possible. While some may prefer complex queries in raw SQL, for some reason I prefer ORM and I&amp;rsquo;m impressed to see how many use cases Django can cover these days. Anything to avoid writing lots of join table SQL 🤣️&lt;/p&gt;
&lt;h3 id=&#34;aiohttp-code&#34;&gt;aiohttp code&lt;/h3&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;async def process_response(monitor, response):
    if response.status == monitor[&amp;quot;expected_status&amp;quot;]:
        if monitor[&amp;quot;expected_body&amp;quot;]:
            if monitor[&amp;quot;expected_body&amp;quot;] in await response.text():
                monitor[&amp;quot;is_up&amp;quot;] = True
            else:
                monitor[&amp;quot;reason&amp;quot;] = MonitorCheckReason.BODY
        else:
            monitor[&amp;quot;is_up&amp;quot;] = True
    else:
        monitor[&amp;quot;reason&amp;quot;] = MonitorCheckReason.STATUS

async def fetch(session, monitor):
    url = monitor[&amp;quot;url&amp;quot;]
    monitor[&amp;quot;is_up&amp;quot;] = False
    start = time.monotonic()
    try:
        if monitor[&amp;quot;monitor_type&amp;quot;] == MonitorType.PING:
            async with session.head(url, timeout=PING_AIOHTTP_TIMEOUT):
                monitor[&amp;quot;is_up&amp;quot;] = True
        elif monitor[&amp;quot;monitor_type&amp;quot;] == MonitorType.GET:
            async with session.get(url, timeout=DEFAULT_AIOHTTP_TIMEOUT) as response:
                await process_response(monitor, response)
        elif monitor[&amp;quot;monitor_type&amp;quot;] == MonitorType.POST:
            async with session.post(url, timeout=DEFAULT_AIOHTTP_TIMEOUT) as response:
                await process_response(monitor, response)
        monitor[&amp;quot;response_time&amp;quot;] = timedelta(seconds=time.monotonic() - start)
    except SSLError:
        monitor[&amp;quot;reason&amp;quot;] = MonitorCheckReason.SSL
    except asyncio.TimeoutError:
        monitor[&amp;quot;reason&amp;quot;] = MonitorCheckReason.TIMEOUT
    except OSError:
        monitor[&amp;quot;reason&amp;quot;] = MonitorCheckReason.UNKNOWN
    return monitor

async def fetch_all(monitors, loop):
    async with aiohttp.ClientSession(loop=loop) as session:
        results = await asyncio.gather(
            *[fetch(session, monitor) for monitor in monitors], return_exceptions=True
        )
        return results
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That&amp;rsquo;s it. Ignoring my models and plenty of Django boilerplate, we have the core of a reasonably performant uptime monitoring system in about 120 lines of code. GlitchTip is MIT licensed so feel free to use as you see fit. I also run a small SaaS service at app.glitchtip.com which helps fund development.&lt;/p&gt;
&lt;h3 id=&#34;on-testing&#34;&gt;On testing&lt;/h3&gt;
&lt;p&gt;I greatly prefer testing in Python over JavaScript. I&amp;rsquo;m pretty sure this 15 line integration test would require a pretty complex Jasmine boilerplate and run about infinite times slower in CI. I will gladly put up with some asyncio boilerplate to avoid testing anything in JavaScript. In my experience, there are Python test driven development fans and there are JS developers who intended to write tests.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    @aioresponses()
    def test_monitor_checks_integration(self, mocked):
        test_url = &amp;quot;https://example.com&amp;quot;
        mocked.get(test_url, status=200)
        with freeze_time(&amp;quot;2020-01-01&amp;quot;):
            mon = baker.make(Monitor, url=test_url, monitor_type=MonitorType.GET)
        self.assertEqual(mon.checks.count(), 1)

        mocked.get(test_url, status=200)
        with freeze_time(&amp;quot;2020-01-01&amp;quot;):
            dispatch_checks()
        self.assertEqual(mon.checks.count(), 1)

        with freeze_time(&amp;quot;2020-01-02&amp;quot;):
            dispatch_checks()
        self.assertEqual(mon.checks.count(), 2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There&amp;rsquo;s a lot going on in little code. I use aioresponses to mock network requests. Django baker to quickly generate DB test data. freezegun to simulate time changes. assertEqual from Django&amp;rsquo;s TestClient. And not seen, CELERY_ALWAYS_EAGER in settings.py to force celery to run synchronously for convenience. I didn&amp;rsquo;t write any async tests code yet I have a pretty decent test covering the core functionality from having monitors in the DB to ensuring they were checked properly.&lt;/p&gt;
&lt;p&gt;JS equivalent&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;describe(&amp;quot;test uptime&amp;quot;, function() {
  it(&amp;quot;should work&amp;quot;, function() {
    // TODO
  });
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Joking aside, I find it quite hard to find a good Node based task runner like Celery, ORM, and test framework that really work well together. There are many little niceties like running Celery in always eager mode that make testing a joy in Python. Let me know in a comment if you disagree and have any JavaScript based solutions you like.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Deploy Saleor E-commerce with Kubernetes and Helm</title>
    <link>https://david.burke.nyc/blog/deploy-saleor-e-commerce-with-kubernetes-and-helm/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/deploy-saleor-e-commerce-with-kubernetes-and-helm/</guid>
    <pubDate>Fri, 04 Jun 2021 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;&lt;a href=&#34;https://saleor.io/&#34;&gt;Saleor&lt;/a&gt; is a headless, Django based e-commerce framework. This post will show how to deploy Saleor using &lt;a href=&#34;https://gitlab.com/burke-software/django-helm-chart&#34;&gt;Django Helm Chart&lt;/a&gt;. It will focus on deploying the Django Backend. The dashboard is a static HTML site and is left out. The front-end is something you should build yourself.&lt;/p&gt;
&lt;p&gt;First you need a Docker image. You can build this yourself or use https://hub.docker.com/r/mirumee/saleor/. I suggest building it yourself so that it&amp;rsquo;s possible to add plugins and set the specific version you&amp;rsquo;d like. Here&amp;rsquo;s a snippet for Gitlab CI as a starting point. This can be run on a fork of &lt;a href=&#34;https://github.com/mirumee/saleor/&#34;&gt;Saleor&lt;/a&gt; which already contains a Dockerfile. I like to tag my image with both the git ref name and short sha for reference later on.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;build:
  stage: build 
  image: docker:20
  services:
    - docker:20-dind
  script:
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, create a values.yml file to power Django Helm Chart. Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;image:
  repository: your-docker-image
  tag: latest

env:
  normal:
    ALLOWED_CLIENT_HOSTS: localhost,127.0.0.1,your-frontend-url
    ALLOWED_HOSTS: &amp;quot;*&amp;quot;
    ENABLE_SSL: &amp;quot;True&amp;quot;
    DEFAULT_EMAIL_FROM: noreply@example.com
    AWS_ACCESS_KEY_ID: XXXXXXXXXX
    AWS_MEDIA_BUCKET_NAME: bucket-name
    AWS_DEFAULT_ACL: public-read
    SENTRY_DSN: https://something@app.glitchtip.com/project-id
  secret:
    DATABASE_URL: your-postgres-connection-string
    SECRET_KEY: your-secret-key
    AWS_SECRET_ACCESS_KEY: XXXXXXXXX
    EMAIL_URL: email-connection-string
    CELERY_BROKER_URL: your-redis-connection-string

web:
  replicaCount: 2
  port: 8000
  args: [&amp;quot;gunicorn&amp;quot;, &amp;quot;--bind&amp;quot;, &amp;quot;:8000&amp;quot;, &amp;quot;--workers&amp;quot;, &amp;quot;4&amp;quot;, &amp;quot;--worker-class&amp;quot;, &amp;quot;uvicorn.workers.UvicornWorker&amp;quot;, &amp;quot;saleor.asgi:application&amp;quot;]
  autoscaling:
    enabled: false
  livenessProbe:
    failureThreshold: 5
    initialDelaySeconds: 5
    timeoutSeconds: 2
    path: &amp;quot;/graphql/&amp;quot;
  readinessProbe:
    failureThreshold: 10
    initialDelaySeconds: 5
    timeoutSeconds: 2
    path: &amp;quot;/graphql/&amp;quot;
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: nginx
    hosts:
      - host: your-host
        paths:
          - path: /
            pathType: ImplementationSpecific

worker:
  enabled: true
  args:
    - celery
    - -A
    - saleor
    - --app=saleor.celeryconf:app
    - worker
    - --loglevel=info

redis:
  architecture: standalone
  auth:
    password: redis-password
  master:
    persistence:
      enabled: false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s break this apart, as there are many options here.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I choose to enable Kubernetes managed Redis but not Postgres. I don&amp;rsquo;t trust running stateful services like Postgres in Kubernetes but you could set postgres.enabled=true.&lt;/li&gt;
&lt;li&gt;Many Saleor settings are managed via environment variables. Documentation for them exists &lt;a href=&#34;https://docs.saleor.io/docs/developer/running-saleor/configuration/&#34;&gt;here&lt;/a&gt;. In the example, I configure AWS S3 and set my DATABASE_URL to a managed Postgres instance like RDS.&lt;/li&gt;
&lt;li&gt;The web and worker args (which maps to Docker&amp;rsquo;s command) are set to the specific run commands for Saleor which uses celery and gunicorn. This is also the place to edit configuration options, such as number of gunicorn workers.&lt;/li&gt;
&lt;li&gt;The example contains a ingress. Don&amp;rsquo;t forget to add a ingress controller like ingress-nginx to your cluster. If you don&amp;rsquo;t need a internet accessible URL, remove it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next add the chart repo (or fork it).&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;helm repo add django https://burke-software.gitlab.io/django-helm-chart/
helm install your-app django/django -f values.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The chart should output instructions on accessing the new site. Make sure to review logs to ensure Celery is running as well. Now you have a Saleor backend running on Kubernetes. If running in production, make sure to review affinity values, Saleor configuration, resource limits, and add tls to the ingress.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Deploy Django with Helm</title>
    <link>https://david.burke.nyc/blog/deploy-django-with-helm/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/deploy-django-with-helm/</guid>
    <pubDate>Mon, 15 Feb 2021 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;This is a follow up post to &lt;a href=&#34;https://davidmburke.com/2020/01/24/deploy-django-with-helm-to-kubernetes/&#34;&gt;Deploy Django with helm to Kubernetes&lt;/a&gt; which focused on the CI pipeline. This post will highlight a generic &lt;a href=&#34;https://gitlab.com/burke-software/django-helm-chart&#34;&gt;Django Helm Chart&lt;/a&gt; I made for &lt;a href=&#34;http://glitchtip.com/&#34;&gt;GlitchTip&lt;/a&gt;, an open source alternative to Sentry issue tracking.&lt;/p&gt;
&lt;p&gt;Note that Kubernetes and Helm are very complex systems that a simple Django app does not need. GlitchTip, for example, can run on &lt;a href=&#34;https://gitlab.com/glitchtip/glitchtip#installing-glitchtip-on-digitalocean-app-platform&#34;&gt;DigitalOcean App Platform&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;django-celery-redis-helm-chart&#34;&gt;Django + Celery + Redis Helm Chart&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://gitlab.com/glitchtip/glitchtip-helm-chart/-/tree/5-more-generic-helm&#34;&gt;Django Helm Chart&lt;/a&gt; can act as a starter Helm Chart for any Django project. While you could use the chart directly, I recommend forking it instead so that you can include your own customizations.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with some language definitions and assumptions&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A django app is made of several &amp;ldquo;components&amp;rdquo; - often the web server, Celery worker queue, Celery Beat, DB migration job, and databases including Postgres and Redis.&lt;/li&gt;
&lt;li&gt;In addition, we need Helm specific infrastructure such as a Service, Load Balancer, and/or Ingress.&lt;/li&gt;
&lt;li&gt;Environment variables will be stored as Helm values. This is a method that can easily be done with Helm alone. You may want a more complex solution for secrets such as &lt;a href=&#34;https://www.vaultproject.io/&#34;&gt;Hashicorp Vault&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I highly recommend installing &lt;a href=&#34;https://github.com/databus23/helm-diff&#34;&gt;Helm Diff&lt;/a&gt;. I wouldn&amp;rsquo;t suggest using Helm without it. One small typo in Helm can delete your entire app.&lt;/li&gt;
&lt;li&gt;This chart supports Helm based Postgres and Redis, however I strongly recommend using a managed service for any persistent data.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;forking-the-helm-chart&#34;&gt;Forking the Helm Chart&lt;/h2&gt;
&lt;p&gt;Start by forking the &lt;a href=&#34;https://gitlab.com/burke-software/django-helm-chart&#34;&gt;Django Helm Chart&lt;/a&gt;. If you&amp;rsquo;d like to keep your own secrets in a values.yaml file - ensure this repo is private.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Edit Chart.yaml and set the name to your project. You may want to edit appVersion, but I find it a little burdensome to increment this. Unless you plan to host a helm chart for many users, it&amp;rsquo;s not necessary.&lt;/li&gt;
&lt;li&gt;Carefully review values.yaml&lt;/li&gt;
&lt;li&gt;Notice the tree structure reflects our web and worker components.&lt;/li&gt;
&lt;li&gt;For new kubernetes users, leave autoscaling off and set replicas manually.&lt;/li&gt;
&lt;li&gt;Defaults for resources are a guess. Adjust them based on your app&amp;rsquo;s real world usage.&lt;/li&gt;
&lt;li&gt;The default web affinity attempts to implement the business rule &amp;ldquo;Do not run all app pods on the same node&amp;rdquo;&lt;ul&gt;
&lt;li&gt;This will only work if you have at least one more node than you need. If one node becomes unhealthy, Kubernetes may be forced to do &amp;ldquo;bad&amp;rdquo; things like schedule all pods on one Node. I recommend at least three Nodes for even a smaller production deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If your app needs to live on the internet, enable the Ingress. You&amp;rsquo;ll also need one Load Balancer for your Kubernetes cluster. Technically you can make just a Load Balancer and ignore the Ingress, but I don&amp;rsquo;t recommend this. Most service providers charge for Load Balancers and it may get expensive running them for staging/testing instances of your app.&lt;/li&gt;
&lt;li&gt;Consider if you need to enable Postgres and Redis. Personally, I always use a managed, non-kubernetes, Postgres server like RDS or DigitalOcean&amp;rsquo;s managed databases. If my Redis instance doesn&amp;rsquo;t need to persist, for example it&amp;rsquo;s used only for cache or a task queue, I enable it in Kubernetes.&lt;/li&gt;
&lt;li&gt;Any key-value under environmentVariables will map to environment variables that can be read manually or by &lt;a href=&#34;https://django-environ.readthedocs.io/en/latest/&#34;&gt;django-environ&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Health checks assume a endpoint at /_health/. I suggest adding this to your Django admin, although you could change it in templates/web/deployment.yaml. It isn&amp;rsquo;t desired to run, for example, your homepage if it involves database lookups. Don&amp;rsquo;t test your entire app infrastructure, only test that Django is responding.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;def health(request):
    return HttpResponse(&amp;quot;ok&amp;quot;, content_type=&amp;quot;text/plain&amp;quot;)

path(&amp;quot;_health/&amp;quot;, health),
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now install your app. Something like&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;helm install your-chart -f your-values.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&#34;whats-next&#34;&gt;What&amp;rsquo;s next?&lt;/h2&gt;
&lt;p&gt;Consider contributing any improvements. Here&amp;rsquo;s a few ideas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Better budget, affinity, autoscaling, and resource defaults.&lt;/li&gt;
&lt;li&gt;Ideas for secrets management.&lt;/li&gt;
&lt;li&gt;~~Enable/Disable features like Celery and more.~~ Done&lt;/li&gt;
&lt;li&gt;Is it worth publishing this chart instead of only forking it?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you found this chart useful, consider donating &lt;a href=&#34;http://gitlab.com/glitchtip/&#34;&gt;time&lt;/a&gt; or &lt;a href=&#34;https://liberapay.com/GlitchTip/donate&#34;&gt;money&lt;/a&gt; to GlitchTip. Donations let me spend more time on various open source projects. GlitchTip is an open source error tracking platform that is compatible with the open source Sentry SDKs. It&amp;rsquo;s a free software alternative to proprietary platforms like DataDog and Sentry. We also offer a &lt;a href=&#34;http://app.glitchtip.com&#34;&gt;paid hosted service&lt;/a&gt;.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Wagtail Single Page App Integration news</title>
    <link>https://david.burke.nyc/blog/wagtail-single-page-app-integration-news/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/wagtail-single-page-app-integration-news/</guid>
    <pubDate>Sun, 28 Jun 2020 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;&lt;a href=&#34;https://gitlab.com/thelabnyc/wagtail-spa-integration&#34;&gt;wagtail-spa-integration&lt;/a&gt; is a Python package I started with coworkers at &lt;a href=&#34;http://thelabnyc.com&#34;&gt;thelab&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;version-20&#34;&gt;Version 2.0&lt;/h2&gt;
&lt;p&gt;Wagtail SPA Integration 2.0 is released! The release is actually maintenance only, but now requires Wagtail 1.8+ thus making it a potentially breaking change.&lt;/p&gt;
&lt;h2 id=&#34;coming-soon-version-30-with-wagtail-headless-preview&#34;&gt;Coming soon version 3.0 with wagtail-headless-preview&lt;/h2&gt;
&lt;p&gt;A major feature of Wagtail SPA Integration is preview support. Torchbox (the creators of Wagtail) developed their own solution called &lt;a href=&#34;https://github.com/torchbox/wagtail-headless-preview&#34;&gt;wagtail-headless-preview&lt;/a&gt;. We&amp;rsquo;ll be migrating to this and it will be a significant breaking change. Aligning with Torchbox&amp;rsquo;s implementation will reduce our maintenance burden and allow for a generally more &amp;ldquo;normal&amp;rdquo; experience. It will also remove a feature/quirk in Wagtail SPA that generated links that could be used for up to one day without authentication.&lt;/p&gt;
&lt;h2 id=&#34;nextjs-support-coming-soon&#34;&gt;NextJS Support coming soon!&lt;/h2&gt;
&lt;p&gt;We have a proof of concept for NextJS support. Preview/contribute it &lt;a href=&#34;https://gitlab.com/thelabnyc/nextjs-wagtail&#34;&gt;here&lt;/a&gt;. This package utilizes NextJS&amp;rsquo;s dynamic routing and dynamic components making it possible base NextJS pages on Wagtail page types. One difference from the Angular Wagtail implementation is that all communication is handled via the NextJS Node server instead of direct Wagtail REST API calls. This results in simpler code, but slightly worse performance.&lt;/p&gt;
&lt;p&gt;Angular Wagtail will also receive a minor update to work with 3.0.&lt;/p&gt;
&lt;h2 id=&#34;shameless-advertising&#34;&gt;Shameless advertising&lt;/h2&gt;
&lt;p&gt;Looking for an open source error monitoring solution for your Django, Angular, or NextJS apps? Try out glitchtip.com. GlitchTip is compatible with Sentry&amp;rsquo;s open source SDK but unlike Sentry is 100% open source. We offer paid SaaS hosting as an option. Your support gives us time to continue work on these various open source projects!&lt;/p&gt;</description>
  </item>
  <item>
    <title>Deploy Django with helm to Kubernetes</title>
    <link>https://david.burke.nyc/blog/deploy-django-with-helm-to-kubernetes/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/deploy-django-with-helm-to-kubernetes/</guid>
    <pubDate>Fri, 24 Jan 2020 12:00:00 +0000</pubDate>
<category>django</category><category>docker</category><category>glitchtip</category><category>kubernetes</category>    <description>&lt;p&gt;This guide attempts to document how to deploy a Django application with Kubernetes while using continuous integration It assumes basic knowledge of Docker and running Kubernetes and will instead focus on using helm with CI. Goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Must be entirely automated and deploy on git pushes&lt;/li&gt;
&lt;li&gt;Must run database migrations once and only once per deploy&lt;/li&gt;
&lt;li&gt;Must revert deployment if migrations fail&lt;/li&gt;
&lt;li&gt;Must allow easy management of secrets via environment variables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My need for this is to deploy &lt;a href=&#34;https://glitchtip.com/&#34;&gt;GlitchTip&lt;/a&gt; staging builds automatically. GlitchTip is an open source error tracking platform that is compatible with Sentry. You can find the finished helm chart and gitlab CI script &lt;a href=&#34;https://gitlab.com/glitchtip/glitchtip-frontend/&#34;&gt;here&lt;/a&gt;. I&amp;rsquo;m using &lt;a href=&#34;https://m.do.co/c/7e90b8fb37f8&#34;&gt;DigitalOcean&lt;/a&gt; and Gitlab CI but this guide will generally work for any Kubernetes provider or Docker based CI tool.&lt;/p&gt;
&lt;h1 id=&#34;building-docker&#34;&gt;Building Docker&lt;/h1&gt;
&lt;p&gt;This guide assumes you have basic familiarity with running Django in Docker. If not, consider a local build first using &lt;a href=&#34;https://docs.docker.com/compose/django/&#34;&gt;docker compose&lt;/a&gt;. I prefer using compose for local development because it&amp;rsquo;s very simple and easy to install.
Build a Docker image and tag it with the git short hash. This will allow us to specify an exact image build later on and will ensure code builds are tied to specific helm deployments. If we used &amp;ldquo;latest&amp;rdquo; instead, we may end up accidentally upgrading the Docker image. Using Gitlab CI the script may look like this:&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker build -t ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME} -t ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This uses -t to tag the new build with the Gitlab CI environment variables to specify the docker registry and tags. It uses &amp;ldquo;ref name&amp;rdquo; which is the tag or branch name. This will result in a tag such as &amp;ldquo;1.3&amp;rdquo; or branch such as &amp;ldquo;dev&amp;rdquo;. This tagging is intended for users who may just want a specific named version or branch. The second -t tags it with the git short hash. This tag will be referenced later on by helm.
Before moving on - make sure you can now &lt;code&gt;docker pull&lt;/code&gt; your CI built image and run it. Make sure to set the Dockerfile CMD to use gunicorn, uwsgi, or another production ready server. We&amp;rsquo;ll deal with Django migrations later using Helm.&lt;/p&gt;
&lt;h1 id=&#34;setting-up-kubernetes&#34;&gt;Setting up Kubernetes&lt;/h1&gt;
&lt;p&gt;This guide assumes you know how to set up Kubernetes. I chose DigitalOcean because they provide managed Kubernetes, it&amp;rsquo;s reasonably priced, and I like supporting smaller companies. DigitalOcean limits choice which makes it easier to use for average looking projects. It doesn&amp;rsquo;t offer the level of customization and services AWS does. If you decide to use DigitalOcean and want to help offset the cost of my open source projects, considering using this affiliate &lt;a href=&#34;https://m.do.co/c/7e90b8fb37f8&#34;&gt;link&lt;/a&gt;. My goals for a hosting platform are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easy to use&lt;/li&gt;
&lt;li&gt;Able to be managed via terraform&lt;/li&gt;
&lt;li&gt;Managed Postgres&lt;/li&gt;
&lt;li&gt;Managed Kubernetes&lt;/li&gt;
&lt;li&gt;Able to restrict network access for internal services such as the database&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whichever platform you are using, make sure you have a database and it&amp;rsquo;s connection string and can authenticate to Kubernetes. If you are new to Kubernetes, I suggest deploying any docker image manually (without tooling like helm) to get a little more familiar. Technically, you could also run your database in Kubernetes and Helm. However I prefer managed stateful services and will not cover running the database in Kubernetes in this guide.&lt;/p&gt;
&lt;h1 id=&#34;deploy-to-kubernetes-with-helm-in-gitlab-ci&#34;&gt;Deploy to Kubernetes with Helm in Gitlab CI&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Update Feb 2021&lt;/strong&gt;
The GlitchTip Helm Chart is now a generic Django + Celery Helm chart. Read more &lt;a href=&#34;https://davidmburke.com/2021/02/15/deploy-django-with-helm/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Now that you have a Docker image and Kubernetes infrastructure, it&amp;rsquo;s time to write a Helm chart and deploy your image automatically from CI. A Helm chart allows you to write Kubernetes yaml configuration templates using variables. The &lt;a href=&#34;https://gitlab.com/glitchtip/glitchtip-helm-chart&#34;&gt;chart&lt;/a&gt; I use for GlitchTip should be a good starting point for most Django apps. At a minimum, read the getting started section for &lt;a href=&#34;https://helm.sh/docs/chart_template_guide/getting_started/&#34;&gt;Helm&amp;rsquo;s documentation&lt;/a&gt;. The GlitchTip chart includes one web server deployment and a Django migration job with helm lifecycle hook. You may need to set up an additional deployment if you use a worker such as Celery. The steps are the same, just override the Docker RUN command to start celery instead of your web server.
Run the initial helm install locally. This is necessary to set initial variables such as the database connection that don&amp;rsquo;t need to be set in CI each deploy. Reference each value to override in your chart&amp;rsquo;s values.yaml. If following my GlitchTip example, that will be databaseURL and secretKey. databaseURL is the Database connection string. I use &lt;a href=&#34;https://django-environ.readthedocs.io/en/latest/#supported-types&#34;&gt;django-environ&lt;/a&gt; to set this. You could also define a separate databaseUser, databasePassword, etc if you like making more work for yourself. The key to make this work is to ensure one way or another the database credentials and other configuration get passed in as environment variables that are read by your settings.py file. Ensure your CI server has built at least one docker image. Place your chart files in the same git repo as your Django project in a directory &amp;ldquo;chart&amp;rdquo;
Run &lt;code&gt;helm install your-app-name ./chart --set databaseURL=string --set secretKey=random_string --set image.tag=git_short_hash&lt;/code&gt;​
If you use GlitchTip&amp;rsquo;s chart - it will not set up a load balancer but it will show output that explains how to connect locally just to test that everything is working. The Django migration job should also run and migrate your database. This guide will not include the many options you have for load balancing. I choose to use DigitalOcean&amp;rsquo;s load balancer and having it directly select the deployment&amp;rsquo;s pods. Note that in Kubernetes, a service of type Load Balancer may run a service providers load balancer and allow you to configure it through kubernetes config yaml. This will vary between providers. Here&amp;rsquo;s a sample load balancer that can be applied with ​kubectl &amp;ndash;namespace your-namespace apply -f load-balancer.yaml note that it uses selector to directly send traffic from the load balancer to pods. It also contains DigitalOcean specific annotations, which is why I can&amp;rsquo;t document a universal way to do this.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: your-app-staging
  annotations:
    service.beta.kubernetes.io/do-loadbalancer-certificate-id: long-id
    service.beta.kubernetes.io/do-loadbalancer-healthcheck-path: /
    service.beta.kubernetes.io/do-loadbalancer-protocol: http
    service.beta.kubernetes.io/do-loadbalancer-redirect-http-to-https: &amp;quot;true&amp;quot;
    service.beta.kubernetes.io/do-loadbalancer-tls-ports: &amp;quot;443&amp;quot;
spec:
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
  - name: https
    port: 443
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/instance: your-app-staging
    app.kubernetes.io/name: your-app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point you should have a fully working Django application.&lt;/p&gt;
&lt;h2 id=&#34;updating-in-ci-using-helm&#34;&gt;Updating in CI using Helm&lt;/h2&gt;
&lt;p&gt;Now set up CI to upgrade your app on git pushes (or other criteria). While technically optional, I suggest making separate namespaces and service accounts for each environment. Unfortunately this process can feel obtuse at first and I felt was the hardest part of this project. For each environment, we need the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Service Account&lt;/li&gt;
&lt;li&gt;Role Binding&lt;/li&gt;
&lt;li&gt;Secret with CA Cert and token&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a rough analogy the service account is a &amp;ldquo;user&amp;rdquo; but for a bot instead of a human. A role binding defines the permissions that something (say a service account) has. The role binding should have the &amp;ldquo;edit&amp;rdquo; permission for the namespace. The secret is like the &amp;ldquo;password&amp;rdquo; but is actually a certificate and token. Read more from Kubernetes &lt;a href=&#34;https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-a-service-account-api-token&#34;&gt;documentation&lt;/a&gt;.
Once this is set up locally, test it out. For example, use the new service account auth in your ~/.kube/config and run kubectrl get pods &amp;ndash;namespace=your-namespace. The CA cert and token from your recently created secret should be what is in your kube&amp;rsquo;s config file. I found no sane manner of editing multiple kubernetes configurations and resorted to manually editing the config file.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: big-long-base64 
    server: https://stuff.k8s.ondigitalocean.com
  name: some-name

...

users:
- name: default
  user:
    token big-long-token-from-secret
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice I used certifate-authority-data so I could reference the cert inline as base64. Next save the entire config file in Gitlab CI under settings, CI, Variables.
&lt;img alt=&#34;Screenshot from 2020-01-24 10-59-53&#34; src=&#34;/static/img/2020/01/screenshot-from-2020-01-24-10-59-53.png&#34;&gt;
There&amp;rsquo;s actually a lot happening in this little bit of configuration. File type in Gitlab CI will cause the value to save into a random tmp file. The key &amp;ldquo;KUBECONFIG&amp;rdquo; will be set to the file location. KUBECONFIG is also the environment variable helm will use to locate the kube config file. Protected will allow this only to be available to protected git branches/tags. If we didn&amp;rsquo;t set protected, someone with only limited git access could make their own branch that runs &lt;code&gt;echo $KUBECONFIG&lt;/code&gt; and view the very confidential data! If set up right, you should now be able to run helm with the authentication that just works.
Finally add the deploy step to Gitlab CI&amp;rsquo;s yaml file.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;deploy-staging:
  stage: deploy
  image: lwolf/helm-kubectl-docker
  script:
    - helm upgrade your-app-staging ./chart --set image.tag=${CI_COMMIT_SHORT_SHA} --reuse-values
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;​stage ensures it runs after the docker build. For image, use lwolf/helm-kubectl-docker which has helm already installed. The script is amazingly just one line thanks to the previous authentication and Gitlab CI variable tricks done. It runs helm upgrade with &amp;ndash;set image.tag to the new git short hash and &amp;ndash;reuse-values allows it to set this new value without overriding previous values. Using helm this way allows you to keep database secrets outside of Gitlab. Do note however that anyone with helm access can read these values. If you need a more robust system then you&amp;rsquo;ll need something like Vault. But even without Vault, we can isolate basic git users who can create branches and admin users who have access to helm and the master branch.
The environment section is optional and let&amp;rsquo;s Gitlab track deploys. &amp;ldquo;only&amp;rdquo; causes the script to only run on the master branch. Alternatively it could be set for other branches or tags.
If you need to change an environment variable, run the same upgrade command locally and &amp;ndash;set as many variables as needed. Keep the &amp;ndash;reuse-values. Because the databaseURL value is marked as required, helm will error instead of erase previous values should you forget the important &amp;ndash;reuse-values.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I like Kubernetes for it&amp;rsquo;s reliability but I find it creates a large amount of decision fatigue. I hope this guide provides one way to do things that I find works. If you have a better way - let me know by commenting here or even open an issue on GlitchTip. I&amp;rsquo;m sure there&amp;rsquo;s room for improvement. For example, I&amp;rsquo;d rather generate the django secret key automatically but helm&amp;rsquo;s random function doesn&amp;rsquo;t let you store it persistently.
I don&amp;rsquo;t like Kube&amp;rsquo;s, maddening at times, complexity. Kubernetes is almost never a solution by itself and requires additional tools to make it work for even very basic use cases. I found Openshift to handle a lot of common use cases like deploy hooks and user/service management much easier. Openshift &amp;ldquo;routes&amp;rdquo; are also defined in standard yaml config rather than forcing the user to deal with propreitary annotations on a Load Balancer. However, I&amp;rsquo;m leery of using Openshift Online considering it hasn&amp;rsquo;t been updated to version 4 and no roadmap seems to exist. It&amp;rsquo;s also quite a bit more expensive (not that it&amp;rsquo;s bad to pay more for good open source software).
Finally if you need error tracking for your Django app and prefer open source solutions - give &lt;a href=&#34;https://glitchtip.com&#34;&gt;GlitchTip&lt;/a&gt; a try. Contributors are preferred, but you can also support the project by using the DigitalOcean affiliate &lt;a href=&#34;https://m.do.co/c/7e90b8fb37f8&#34;&gt;link&lt;/a&gt; or &lt;a href=&#34;https://en.liberapay.com/GlitchTip/donate&#34;&gt;donating&lt;/a&gt;. &lt;a href=&#34;https://burkesoftware.com/&#34;&gt;Burke Software&lt;/a&gt; also offers paid consulting services for open source software hosting and software development.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Django Rest Framework ModelViewSets with natural key lookup</title>
    <link>https://david.burke.nyc/blog/django-rest-framework-modelviewsets-with-natural-key-lookup/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/django-rest-framework-modelviewsets-with-natural-key-lookup/</guid>
    <pubDate>Wed, 04 Dec 2019 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;DRF ModelViewSet can easily support detail views by slug via the lookup_value attribute. But what if you had compound keys (aka natural keys)? For example a url structure like&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/api/computers/&amp;lt;organization-slug&amp;gt;/&amp;lt;computer-slug&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A computer slug may only be unique per organization. That means different organizations may have computers with the same slug. But no computer may have the same slug in one organization. By using both slugs, we can look up a specific computer. We can use the lookup_value_regex attribute for this.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;class ComputerViewSet(viewsets.ModelViewSet):
    queryset = Computer.objects.all()
    serializer_class = ComputerSerializer
    lookup_value_regex = r&amp;quot;(?P&amp;lt;org_slug&amp;gt;[^/.]+)/(?P&amp;lt;slug&amp;gt;[-\w]+)&amp;quot;

    def get_object(self):
        queryset = self.filter_queryset(self.get_queryset())
        obj = get_object_or_404(
            queryset,
            slug=self.kwargs[&amp;quot;slug&amp;quot;],
            organization__slug=self.kwargs[&amp;quot;org_slug&amp;quot;],
        )

        # May raise a permission denied
        self.check_object_permissions(self.request, obj)

        return obj
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This works with drf-nested-routers. For example, we could add a nested /hard_drives viewset. The url values are in self.kwargs.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;class HardDriveViewSet(viewsets.ModelViewSet):
    queryset = HardDrive.objects.all()
    serializer_class = HardDriveSerializer

    def get_queryset(self):
        return (
            super()
            .get_queryset()
            .filter(
                computer__slug=self.kwargs[&amp;quot;slug&amp;quot;],
                computer__organization__slug=self.kwargs[&amp;quot;org_slug&amp;quot;],
            )
        )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
  </item>
  <item>
    <title>Angular Wagtail 1.0 and getting started</title>
    <link>https://david.burke.nyc/blog/angular-wagtail-1-0-and-getting-started/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/angular-wagtail-1-0-and-getting-started/</guid>
    <pubDate>Sun, 20 Oct 2019 12:00:00 +0000</pubDate>
<category>angular</category><category>python</category><category>test</category>    <description>&lt;p&gt;&lt;a href=&#34;https://gitlab.com/thelabnyc/angular-wagtail&#34;&gt;Angular Wagtail&lt;/a&gt; and &lt;a href=&#34;https://gitlab.com/thelabnyc/wagtail-spa-integration&#34;&gt;Wagtail Single Page App Integration&lt;/a&gt; are officially 1.0 and stable. It&amp;rsquo;s time for a more complete getting started guide. Let&amp;rsquo;s build a new app together. Our goal will be to make a multi-site enabled Wagtail CMS with a separate Angular front-end.  When done, we&amp;rsquo;ll be set up for features such as&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Map Angular components to Wagtail page types to build any website tree we want from the CMS&lt;/li&gt;
&lt;li&gt;All the typical wagtail features we expect, drafts, redirects, etc. No compromises.&lt;/li&gt;
&lt;li&gt;SEO best practices including server side rendering with Angular Universal, canonical urls, and meta tags.&lt;/li&gt;
&lt;li&gt;Correct status codes for redirects and 404 not found&lt;/li&gt;
&lt;li&gt;Lazy loaded modules&lt;/li&gt;
&lt;li&gt;High performance, cache friendly, small JS bundle size (In my experience 100kb - 270kb gzipped for large scale apps)&lt;/li&gt;
&lt;li&gt;Absolutely no jank. None. When a page loads we get the full page. Nothing &amp;ldquo;pops in&amp;rdquo; unless we want it to. No needless dom redraws that you may see with some single page apps.&lt;/li&gt;
&lt;li&gt;Scalable - add more sites, add translations, keep just one &amp;ldquo;headless&amp;rdquo; Wagtail instance to manage it all.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Start with a Wagtail project that has wagtail-spa-integration added. For demonstration purposes, I will use the sandbox project in wagtail-spa-integration with Docker. Feel free to use your own Wagtail app instead.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;git clone https://gitlab.com/thelabnyc/wagtail-spa-integration.git&lt;/li&gt;
&lt;li&gt;Install docker and docker-compose&lt;/li&gt;
&lt;li&gt;docker-compose up&lt;/li&gt;
&lt;li&gt;docker-compose run &amp;ndash;rm web ./manage.py migrate&lt;/li&gt;
&lt;li&gt;docker-compose run &amp;ndash;rm web ./manage.py createsuperuser&lt;/li&gt;
&lt;li&gt;Go to http://localhost:8000/admin/ and log in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Set up Wagtail Sites. We will make 1 root page and multiple homepages representing each site.
&lt;img alt=&#34;Screenshot from 2019-10-20 12-08-46&#34; src=&#34;/static/img/2019/10/screenshot-from-2019-10-20-12-08-46.png&#34;&gt;
You may want to rename the &amp;ldquo;Welcome to Wagtail&amp;rdquo; default page to &amp;ldquo;API Root&amp;rdquo; just for clarity. Then create two child pages of any type to act as homepages. If you don&amp;rsquo;t need multi-site support, just add one instead. Wagtail requires the Sites app to be enabled even if only one site is present. The API Root will still be important later on for distinguishing the Django API server from the front-end Node server.
Next head over to Settings, Sites. Keep the default Site attached to the API Root page. Add another Site for each homepage. If you intend to have two websites, you should have three Wagtail Sites (API Root, Site A, Site B). Each hostname + port combination must be unique. For local development, it doesn&amp;rsquo;t matter much. For production you may have something like api.example.com, www.example.com, and intranet.example.com.
&lt;img alt=&#34;Screenshot from 2019-10-20 15-13-39&#34; src=&#34;/static/img/2019/10/screenshot-from-2019-10-20-15-13-39.png&#34;&gt;
Next let&amp;rsquo;s set up the Wagtail API. This is already done for you in the sandbox project but when integrating your own app, you may follow the docs &lt;a href=&#34;http://docs.wagtail.io/en/v2.6.2/advanced_topics/api/&#34;&gt;here&lt;/a&gt;. Then follow Wagtail SPA Integration &lt;a href=&#34;https://gitlab.com/thelabnyc/wagtail-spa-integration#usage&#34;&gt;docs&lt;/a&gt; to set up the extended Pages API. Make sure to set WAGTAILAPI_BASE_URL to localhost:8000 if you want to run the site locally on port 8000. Here&amp;rsquo;s an example of setting up routes.
api.py&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;from wagtail.api.v2.router import WagtailAPIRouter
from wagtail_spa_integration.views import SPAExtendedPagesAPIEndpoint

api_router = WagtailAPIRouter(&amp;#39;wagtailapi&amp;#39;)
api_router.register_endpoint(&amp;#39;pages&amp;#39;, SPAExtendedPagesAPIEndpoint)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;urls.py&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;from django.conf.urls import include, url
from wagtail.core import urls as wagtail_urls
from wagtail_spa_integration.views import RedirectViewSet
from rest_framework.routers import DefaultRouter
from .api import api_router

router = DefaultRouter()
router.register(r&amp;#39;redirects&amp;#39;, RedirectViewSet, basename=&amp;#39;redirects&amp;#39;)

urlpatterns = [
    url(r&amp;#39;^api/v2/&amp;#39;, api_router.urls),
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Test this out by going to localhost:8000/api/ and localhost:8000/api/v2/pages/
If you&amp;rsquo;d like to enable the Wagtail draft feature - set PREVIEW_DRAFT_CODE in settings.py to any random string. Note this feature will generate special one time, expiring links that do not require authentication to view drafts. This is great for sharing and the codes expire in one day. However if your drafts contain more sensitive data, you may want to add authentication to the Pages API. This is out of scope for Wagtail SPA Integration, but consider using any standard Django Rest Framework authentication such as tokens or JWT. You may want to check if a draft code is present and only check authentication then, so that the normal pages API is public.&lt;/p&gt;
&lt;h2 id=&#34;angular-front-end&#34;&gt;Angular Front-end&lt;/h2&gt;
&lt;p&gt;Now let&amp;rsquo;s add a new Angular app (or modify an existing one).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ng new angular-wagtail-demo&lt;/li&gt;
&lt;li&gt;cd angular-wagtail-demo&lt;/li&gt;
&lt;li&gt;npm i angular-wagtail &amp;ndash;save&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In app.module.ts add&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;import { WagtailModule } from &amp;#39;angular-wagtail&amp;#39;;
WagtailModule.forRoot({
  pageTypes: [],
  wagtailSiteDomain: &amp;#39;http://localhost:8000&amp;#39;,
  wagtailSiteId: 2,
}),
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In app-routing.module.ts add&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;import { CMSLoaderGuard, CMSLoaderComponent } from &amp;#39;angular-wagtail&amp;#39;;
const routes: Routes = [{ path: &amp;#39;**&amp;#39;, component: CMSLoaderComponent, canActivate: [CMSLoaderGuard] }];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is the minimal configuration. Notice the domain and site ID are set explicitly. This is not required as Wagtail can determine the appropriate site based on domain. However, it&amp;rsquo;s much easier to set it explicitly so that we don&amp;rsquo;t have to set up multiple hostnames for local development. Next let&amp;rsquo;s add a lazy loaded homepage module. Making even the homepage lazy loaded will get us in the habit of making everything a lazy loaded module which improves performance for users who might not visit the homepage first (Such as an ad or search result to a specific page).&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ng generate module home --routing
ng generate component home
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In app.module.ts add a &amp;ldquo;page type&amp;rdquo;. An Angular Wagtail page type is a link between Wagtail Page Types and Angular components. If we make a Wagtail page type &amp;ldquo;cms_django_app.HomePage&amp;rdquo; we can link it to an Angular component &amp;ldquo;HomeComponent&amp;rdquo;. Page types closely follow the Angular Router, so any router features like resolvers will just work with exactly the same syntax. In fact, angular-wagtail uses the Angular router behind the scenes.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pageTypes: [
  {
    type: &amp;#39;sandbox.BarPage&amp;#39;,
    loadChildren: () =&amp;gt; import(&amp;#39;./home/home.module&amp;#39;).then(m =&amp;gt; m.HomeModule)
  },
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This maps sandbox.BarPage from the wagtail-spa-integration sandbox to the HomeModule. &amp;ldquo;sandbox&amp;rdquo; is the django app name while BarPage is the model name. This is the same syntax as seen in the Wagtail Pages API and many other places in django to refer to a model (app_label.model). &amp;ldquo;loadChildren&amp;rdquo; is the same syntax as the Angular Router. I could set the component instead of loadChildren if I didn&amp;rsquo;t want lazy loading.
Next edit home/home-routing.module.ts. Since our homepage has only one component, set it to always load that component
home-routing.module.ts with WagtailModule.forFeature&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;const routes: Routes = [{
  path: &amp;#39;&amp;#39;,
  component: HomeComponent
}];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To test everything is working run ​&amp;rdquo;npm start&amp;rdquo; and go to localhost:4200.
&lt;img alt=&#34;Screenshot from 2019-10-20 14-47-23&#34; src=&#34;/static/img/2019/10/screenshot-from-2019-10-20-14-47-23.png&#34;&gt;
We now have a home page! However, it doesn&amp;rsquo;t contain any actual CMS data. Let&amp;rsquo;s start by adding the page&amp;rsquo;s title. We could get this data on ngOnInit however this would load the data asynchronously after the route is loaded. This can lead to jank because any static content would load immediately on route completion but async data would pop in later. To fix this, we&amp;rsquo;ll use a resolver. Resolvers can get async data &lt;em&gt;before&lt;/em&gt; the route completes.
Edit home-routing.module.ts&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;import { GetPageDataResolverService } from &amp;#39;angular-wagtail&amp;#39;;
const routes: Routes = [{
  path: &amp;#39;&amp;#39;,
  component: HomeComponent,
  resolve: { cmsData: GetPageDataResolverService }
}];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This resolver service will assign an Observable with the CMS data for use in the component. We can use it in our component:
home.component.ts&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;import { ActivatedRoute } from &amp;#39;@angular/router&amp;#39;;
import { Observable } from &amp;#39;rxjs&amp;#39;;
import { map } from &amp;#39;rxjs/operators&amp;#39;;
import { IWagtailPageDetail } from &amp;#39;angular-wagtail&amp;#39;;

interface IHomeDetails extends IWagtailPageDetail {
  extra_field: string;
}

@Component({
  selector: &amp;#39;app-home&amp;#39;,
  template: `
    &amp;lt;p&amp;gt;Home Works!&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;{{ (cmsData$ | async).title }}&amp;lt;/p&amp;gt;
  `,
})
export class HomeComponent implements OnInit {
  public cmsData$: Observable&amp;lt;IHomeDetails&amp;gt;;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.cmsData$ = this.route.data.pipe(map(dat =&amp;gt; dat.cmsData));
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Going top to bottom, notice how IHomeDetails extends IWagtailPageDetail and adds page specific fields. This should mimic the fields you added when defining the Wagtail Page model. Default Wagtail fields like &amp;ldquo;title&amp;rdquo; are included in IWagtailPageDetail.
The template references the variable cmsData$ which is an Observable with all page data as given by the Wagtail Pages API detail view.
ngOnInit is where we set this variable, using route.data. Notice how cmsData is available from the resolver service. When you load the page, you should notice &amp;ldquo;Home Works!&amp;rdquo; and the title you set in the CMS load at the same time. Nothing &amp;ldquo;pops in&amp;rdquo; which can look bad.
&lt;img alt=&#34;Screenshot from 2019-10-20 15-15-59.png&#34; src=&#34;/static/img/2019/10/screenshot-from-2019-10-20-15-15-59.png&#34;&gt;
At this point, you have learned the basics of using Angular Wagtail!&lt;/p&gt;
&lt;h2 id=&#34;adding-a-lazy-loaded-module-with-multiple-routes&#34;&gt;Adding a lazy loaded module with multiple routes&lt;/h2&gt;
&lt;p&gt;Sometimes it&amp;rsquo;s preferable to have one module with multiple components. For example, there may be 5 components and two of them represent route-able pages. Keeping them grouped in a module increases code readability and makes sense to lazy load the components together. To enable this, make use of WagtailModule.forFeature. Let&amp;rsquo;s try making a &amp;ldquo;FooModule&amp;rdquo; example to demonstrate.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ng generate module foo
ng generate component foo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Edit foo.module.ts&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;import { NgModule, ComponentFactoryResolver } from &amp;#39;@angular/core&amp;#39;;
import { CommonModule } from &amp;#39;@angular/common&amp;#39;;
import { WagtailModule, CoalescingComponentFactoryResolver } from &amp;#39;angular-wagtail&amp;#39;;
import { FooComponent } from &amp;#39;./foo.component&amp;#39;;

@NgModule({
  declarations: [FooComponent],
  entryComponents: [FooComponent],
  imports: [
    CommonModule,
    WagtailModule.forFeature([
      {
        type: &amp;#39;sandbox.FooPage&amp;#39;,
        component: FooComponent
      }
    ])
  ]
})

export class FooModule {
  constructor(
    coalescingResolver: CoalescingComponentFactoryResolver,
    localResolver: ComponentFactoryResolver
  ) {
    coalescingResolver.registerResolver(localResolver);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;FooComponent is added to both declarations and entryComponents as it&amp;rsquo;s not directly added to the router. WagtailModule.forFeature will link the wagtail page type with a component. You can also add a resolver here if needed. Lastly, the constructor adds coalescingResolver. This enabled dynamic component routing between modules and likely won&amp;rsquo;t be needed in Angular 9 with Ivy and future versions of Angular Wagtail.
Add as many types of page types as desired.&lt;/p&gt;
&lt;h2 id=&#34;angular-universal&#34;&gt;Angular Universal&lt;/h2&gt;
&lt;p&gt;Angular Universal can generate pages in Node (or prerender them). This is nice for SEO and general performance. The effect is to generate a minimalist static view of the page that runs without JS enabled. Later the JS bundle is loaded and any dynamic content (shopping carts, user account info) is loaded in. Because the server side rendered static page is always the same for all users, it works great with a CDN. I&amp;rsquo;ve found even complex pages will be around 50kb of data for the first dom paint. &lt;a href=&#34;https://angular.io/guide/universal&#34;&gt;Installation is easy&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ng add @nguniversal/express-engine --clientProject angular.io-example
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Compile with &lt;code&gt;npm run build:ssr&lt;/code&gt;and serve with &lt;code&gt;npm run serve:ssr&lt;/code&gt;​. Angular Wagtail supports a few environment variables we can set in node. Setting the API server domain and site per deployment is possible:&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;export WAGTAIL_SITE_ID=2
export CMS_DOMAIN=http://localhost:8000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Confirm it&amp;rsquo;s working by disabling JavaScript in your browser.
Angular Wagtail provides a few extras for Angular Universal when run in Node (serve:ssr). You can return 404, 302, and 301 status codes by editing server.ts as &lt;a href=&#34;https://gitlab.com/thelabnyc/angular-wagtail#ssr-node-status-code-support&#34;&gt;documented&lt;/a&gt;. You can also add the &lt;a href=&#34;https://gitlab.com/thelabnyc/angular-wagtail#sitemapxml-support&#34;&gt;wagtail generated sitemap&lt;/a&gt;. Not directly related to Wagtail, but I found &lt;a href=&#34;https://helmetjs.github.io&#34;&gt;helmet&lt;/a&gt; and adding a robots.txt pretty helpful too. Angular Univeral just runs express, so anything possible in express is possible in Angular Universal.&lt;/p&gt;
&lt;h2 id=&#34;bells-and-whistles-not-found-and-more-seo&#34;&gt;Bells and whistles - not found and more SEO&lt;/h2&gt;
&lt;p&gt;For a real site, consider adding a 404 not found component, setting page meta tags and canonical url. Edit the WagtailModule.forRoot configuration to modify this however you wish. If you followed the server set up from above then Wagtail redirects and drafts should &amp;ldquo;just work&amp;rdquo;. Any time Angular Wagtail can&amp;rsquo;t match a url path to component, it will query the Wagtail SPA Integration redirects API and will redirect if it finds one. If not, Angular Wagtail will show the 404 not found component to the user.
You can find the full angular wagtail demo source on &lt;a href=&#34;https://gitlab.com/bufke/angular-wagtail-demo&#34;&gt;gitlab&lt;/a&gt;.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Talk: Integrating Angular with &#34;Headless&#34; Wagtail CMS</title>
    <link>https://david.burke.nyc/blog/talk-integrating-angular-with-headless-wagtail-cms/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/talk-integrating-angular-with-headless-wagtail-cms/</guid>
    <pubDate>Wed, 28 Aug 2019 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;I gave a talk at the Django NYC meetup. Here is a &lt;a href=&#34;https://docs.google.com/presentation/d/1XdWtVj0QfPd0HucoW8ttVMGPVdz2y8-JTEMkNj6Rrhg/edit?usp=sharing&#34;&gt;link&lt;/a&gt;.
I spoke about &lt;a href=&#34;https://gitlab.com/thelabnyc/wagtail-spa-integration&#34;&gt;Wagtail SPA Integration&lt;/a&gt; and &lt;a href=&#34;https://gitlab.com/thelabnyc/wagtail-spa-integration&#34;&gt;Angular Wagtail&lt;/a&gt;. I am hoping to call both projects 1.0 in September. Please give them and test and report bugs!&lt;/p&gt;</description>
  </item>
  <item>
    <title>Controlling a ceiling fan with Simple Fan Control</title>
    <link>https://david.burke.nyc/blog/controlling-a-ceiling-fan-with-simple-fan-control/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/controlling-a-ceiling-fan-with-simple-fan-control/</guid>
    <pubDate>Sun, 30 Jun 2019 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;I released Simple Fan Control today on &lt;a href=&#34;https://play.google.com/store/apps/details?id=com.burkesoftware.simplefancontrol&#34;&gt;Google Play&lt;/a&gt;, &lt;a href=&#34;https://burke-software.gitlab.io/simple-fan-control&#34;&gt;web&lt;/a&gt;, and source on &lt;a href=&#34;https://gitlab.com/burke-software/simple-fan-control&#34;&gt;Gitlab&lt;/a&gt;. This project&amp;rsquo;s genesis was the purchase of a Hunter Advocate fan with Internet connectivity. It&amp;rsquo;s app doesn&amp;rsquo;t work, which I wrote about &lt;a href=&#34;https://davidmburke.com/2019/06/01/how-to-set-up-a-hunter-fan-wifi-control-by-decompiling-their-app/&#34;&gt;recently&lt;/a&gt;.
&lt;img alt=&#34;Screenshot from 2019-06-30 11-41-05&#34; src=&#34;/static/img/2019/06/screenshot-from-2019-06-30-11-41-05.png&#34;&gt; Simple Fan Control&amp;rsquo;s web version
The app is build with NativeScript and works by interacting with Alya Network&amp;rsquo;s Internet of Things (IoT) service. If there was interest, I would explore communicating with the fan directly instead of through Alya Networks. The IoT world scares me a bit because users transmit personal data to a third party service they may not be aware even exists. Alya&amp;rsquo;s service collects name, address, email, and GPS coordinates. It&amp;rsquo;s scary to think what this data could be used for or it being leaked. There&amp;rsquo;s also the concern that the fan control app becomes useless if the internet is down or should the company shut it down.
If you are using a Alya Networks based device or want to collaborate on using the code for other IoT projects please let me know by opening a &lt;a href=&#34;https://gitlab.com/burke-software/simple-fan-control/issues&#34;&gt;Gitlab issue&lt;/a&gt;. I&amp;rsquo;m charging $4 for the app, but you can of course build it yourself for source. By &lt;a href=&#34;https://play.google.com/store/apps/details?id=com.burkesoftware.simplefancontrol&#34;&gt;purchasing the app&lt;/a&gt;, you&amp;rsquo;d support further development. Alya Networks dev boards aren&amp;rsquo;t free and would let me test out other configurations and device wifi connectivity.
I do consulting work if you are a IoT company looking to improve your software. Get in touch with info at burkesoftware.com if you&amp;rsquo;d like to know more.&lt;/p&gt;</description>
  </item>
  <item>
    <title>How to set up a Hunter fan Wifi control by decompiling the app</title>
    <link>https://david.burke.nyc/blog/how-to-set-up-a-hunter-fan-wifi-control-by-decompiling-their-app/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/how-to-set-up-a-hunter-fan-wifi-control-by-decompiling-their-app/</guid>
    <pubDate>Sat, 01 Jun 2019 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; I made an &lt;a href=&#34;http://gitlab.com/burke-software/simple-fan-control/&#34;&gt;open source web/android app&lt;/a&gt; that can do this for you. Note you&amp;rsquo;ll still need the SimpleConnect app to connect the fan to wifi.
I got a hunter fan recently that was supposed to be controllable via an app called &lt;a href=&#34;https://play.google.com/store/apps/details?id=com.hunter.agilelink&#34;&gt;SimpleConnect&lt;/a&gt;. Looking at the reviews, it doesn&amp;rsquo;t work. It gets stuck on the email verification step. You get an email link that opens in the app and does nothing.
I decided to inspect the apk file with dex2jar and JD-GUI. All the confirm account step actually has to do is send a PUT request to a url with a token from the email. No need for an app at all really.&lt;/p&gt;
&lt;h2 id=&#34;confirming-the-account&#34;&gt;Confirming the account&lt;/h2&gt;
&lt;p&gt;To confirm the email sign up and get the email link. It should look something like https://app-launcher.aylanetworks.com/launch?custom_url=aylacontrol://user_sign_up_token?token=XXXXXXXX
All we need is the token. The app is supposed to then make a PUT request to https://user.aylanetworks.com/users/confirmation.json with a payload of&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
    &amp;quot;confirmation_token&amp;quot;: &amp;quot;XXXXXXXX&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can do this yourself in postman. Just enter the payload in the Body tab as raw JSON.
&lt;strong&gt;&lt;img alt=&#34;fan_confirm&#34; src=&#34;/static/img/2019/06/fan_confirm.png&#34;&gt;&lt;/strong&gt;
You should get a response with the same personal data you entered before. It should include approved: true.&lt;/p&gt;
&lt;h2 id=&#34;adding-the-fan&#34;&gt;Adding the fan&lt;/h2&gt;
&lt;p&gt;The next issue you&amp;rsquo;ll face is that the QR code is printed too small with very poor quality. The app also seems to set the camera in some sort of poor quality mode. I tried a couple devices and eventual got the code to scan with a Pixel C tablet.
Pretty amazing Ayla Networks made such a worthless app. No testing at all. But what do you expect with internet of things devices.&lt;/p&gt;
&lt;h2 id=&#34;google-assistant-integration&#34;&gt;Google Assistant Integration&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s not that hard to set up, but it&amp;rsquo;s not well documented. You just go &lt;a href=&#34;https://assistant.google.com/services/a/uid/0000007a8237aa4e?hl=en-US&#34;&gt;here&lt;/a&gt; to set it up after setting up the simple connect wifi app. It&amp;rsquo;s a bit clunky saying &amp;ldquo;tell simple connect to do something&amp;rdquo; but it works.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Using Angular with a &#34;headless&#34;  Wagtail CMS</title>
    <link>https://david.burke.nyc/blog/using-angular-with-a-headless-wagtail-cms/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/using-angular-with-a-headless-wagtail-cms/</guid>
    <pubDate>Sat, 11 May 2019 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;Wagtail is a great Django-based content management system. Angular is a full-featured JavaScript framework. I wanted to use them together, so I made some helper libraries. Below, I explain how I did it.
Goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enable Wagtail features like preview and redirects.&lt;/li&gt;
&lt;li&gt;Allow routing to be defined (mostly) in Wagtail&lt;/li&gt;
&lt;li&gt;Maintain great performance through&lt;/li&gt;
&lt;li&gt;Lazy loading JS modules&lt;/li&gt;
&lt;li&gt;Compatible with Angular Universal for server side rendering&lt;/li&gt;
&lt;li&gt;Ensure Wagtail Multi-site functionality works&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;wagtail-and-angular&#34;&gt;wagtail and angular&lt;/h1&gt;
&lt;h1 id=&#34;setting-up-wagtail&#34;&gt;Setting up Wagtail&lt;/h1&gt;
&lt;p&gt;Install from pypi wagtail_spa_integration using the instructions &lt;a href=&#34;https://gitlab.com/thelabnyc/wagtail-spa-integration&#34;&gt;here&lt;/a&gt;. Since there is nothing angular specific about it - you could also use this with other front-end solutions. This package provides an extended Wagtail V2 Pages Endpoint.&lt;/p&gt;
&lt;h1 id=&#34;setting-up-angular&#34;&gt;Setting up Angular&lt;/h1&gt;
&lt;p&gt;Install &lt;a href=&#34;https://gitlab.com/thelabnyc/angular-wagtail&#34;&gt;angular-wagtail&lt;/a&gt;. Follow the instructions. At a high level, instead of setting routes to components, you will set Wagtail page types to either components or modules. For example the Wagtail Page &amp;ldquo;foo.MyPage&amp;rdquo; might map to MyPageComponent in Angular. I will call this dynamic routing, as opposed to Angular router&amp;rsquo;s fixed routes. This is all that&amp;rsquo;s needed for simple websites. However, angular-wagtail works by having the Angular project request page data for every page. This is a problem if your site has thousands of blog pages. Your Angular app may not need to know every blog URL up front. It just needs to know that they follow a schema like &amp;ldquo;blog/blog-post-slug&amp;rdquo;. You can make a lazy loaded module for blog and set the route like&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
  type:&amp;quot;cool_blog.BlogIndexPage&amp;quot;,
  loadChildren:&amp;quot;./blog/blog.module#BlogModule&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are some limitations. loadChildren won&amp;rsquo;t work with nested routes. If you have two components in BlogModule, then you can&amp;rsquo;t both lazy load the module and use the dynamic Wagtail driven routes. There are two workarounds. Ensure the lazy loaded modules only have one route or keep them in Angular&amp;rsquo;s routing instead of WagtailModule&amp;rsquo;s page type mapping. In the blog example, you may have a blog index page and a blog post page nested under. As long as you assume the route is always /blog and /blog/post-slug you don&amp;rsquo;t really need the dynamic routing that WagtailModule provides.
Finally you need to gather page detail data in our components. In the ngOnInit function for add something like&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;constructor(private wagtail: WagtailService) {}
...
this.cmsData$=this.wagtail.getPageForCurrentUrl&amp;lt;IMyCoolPage&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;IMyCoolPage is the interface for the data you expect to receive from wagtail for this page. This works with both fixed routes in Angular router and dynamic routes in WagtailModule.
These functions will also automatically check for redirects if a page is not found.&lt;/p&gt;
&lt;h1 id=&#34;closing-thoughts&#34;&gt;Closing thoughts&lt;/h1&gt;
&lt;p&gt;I really enjoy having all view logic in Angular instead of attempting to mix Django templates with a JS framework&amp;rsquo;s view layer (JSX, Angular&amp;rsquo;s templates, etc). Previously, this meant giving up a lot of features that &amp;ldquo;just work&amp;rdquo; in Wagtail. With using these packages, I can quickly bootstrap a headless Wagtail server with a separate Node/JavaScript front-end and keep all the features I&amp;rsquo;m used to. Please consider contributing some code, unit tests, or make a JS integration with your favorite framework.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Review: Pixel Slate for Linux and Web Development</title>
    <link>https://david.burke.nyc/blog/review-pixel-slate-for-linux-and-web-development/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/review-pixel-slate-for-linux-and-web-development/</guid>
    <pubDate>Mon, 24 Dec 2018 12:00:00 +0000</pubDate>
<category>Chrome OS</category><category>crostini</category><category>pixel slate</category><category>review</category>    <description>&lt;p&gt;The Pixel Slate (i7 model) can be a decent computer for web development, including Docker, Node, and Android development. My workplace recently got me one so I decided to review it for anyone curious about using it for Linux-based development.&lt;/p&gt;
&lt;h2 id=&#34;performance&#34;&gt;Performance&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m reviewing the highest-end version with an i7-8500Y CPU. Let&amp;rsquo;s break that down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Y series is the 5 watt low power offering (not to be confused with the 15 watt U series which is for &amp;ldquo;Ultrabooks&amp;rdquo;).  This allows the Slate to not have any vents or fans, making it perfectly quiet.&lt;/li&gt;
&lt;li&gt;The 8 stands for 8th generation which is the newest generation for the Y series.&lt;/li&gt;
&lt;li&gt;The &amp;ldquo;i7&amp;rdquo; means it&amp;rsquo;s both more expensive and faster than the same class i5. But that doesn&amp;rsquo;t mean an i7 Y series is going to be faster than a very old i3 desktop K series CPU. It&amp;rsquo;s essentially the same chip with more cores enabled and a higher clock frequency.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The i7-8500Y is considerably slower than a roughly equivalent i7-8550U as seen in the XPS 13 9370. (See my review of the &lt;a href=&#34;https://davidmburke.com/2018/02/09/review-dell-xps-13-9370-developer-edition/&#34;&gt;XPS 13 developer edition here&lt;/a&gt;). Since I have both, I&amp;rsquo;ll do a few comparisons. All tests on the XPS 13 are run on Ubuntu 18.10.
&lt;strong&gt;Basemark Web 3.0&lt;/strong&gt;
Pixel Slate - &lt;a href=&#34;https://web.basemark.com/result/?4O61oRcm&#34;&gt;500.4&lt;/a&gt;
XPS 13 - 365.8
Wow - the Slate beats the XPS here - this is surprising! Both are running Chrome. My guess is that Chrome on the Slate has far better optimized drivers than stock Ubuntu on the XPS. This probably allowed the GPU to do more of the work, resulting in a higher score.
&lt;strong&gt;Webpack&lt;/strong&gt;
I tested building &lt;a href=&#34;https://passit.io&#34;&gt;Passit&lt;/a&gt;, the open source password manager I&amp;rsquo;m working on. Passit is built with angular-cli and uses webpack to build bundles. See the repo &lt;a href=&#34;https://gitlab.com/passit/passit-frontend/&#34;&gt;here&lt;/a&gt; if you want to compare. I ran a development build with &amp;ldquo;npm run build&amp;rdquo;
Pixel Slate - 16 seconds
XPS 13 - 11 seconds
&lt;strong&gt;CPU benchmark&lt;/strong&gt;
I ran &amp;ldquo;sysbench &amp;ndash;test=cpu &amp;ndash;cpu-max-prime=20000 &amp;ndash;num-threads=8 run&amp;rdquo;
Pixel Slate - 5.8 seconds
XPS 13 - 10.0 seconds
Lower is better - and the Slate wins. I don&amp;rsquo;t understand this. It should be a simple CPU test, and the XPS 13 has a faster CPU with more cores. Since this test had odd results, I ran &amp;ldquo;stress-ng &amp;ndash;cpu 6 &amp;ndash;cpu-method matrixprod &amp;ndash;metrics-brief &amp;ndash;perf -t 60&amp;rdquo; too.
6 cores:
Pixel Slate: 22839 ops
XPS 13: 46106 ops
2 cores:
Pixel Slate: 25464 ops
XPS 13: 33069 ops
This time the XPS got more than twice as many operations done in the 6 core test - presumably due to its extra cores. Even with just 2 cores, the XPS is still faster.&lt;/p&gt;
&lt;h3 id=&#34;docker-and-django&#34;&gt;Docker and Django&lt;/h3&gt;
&lt;p&gt;As an example of back-end development, I&amp;rsquo;ll run the &lt;a href=&#34;https://gitlab.com/passit/passit-backend/&#34;&gt;passit-backend&lt;/a&gt; (Django) tests in Docker. This shows the time required for creating a PostgreSQL database and running the Python tests. I ran:
- docker-compose up db
- time docker-compose run &amp;ndash;rm web ./manage.py test
Pixel Slate - 38 seconds
XPS 13 - 26 seconds
This test involves a mix of CPU and I/O bound operations. It&amp;rsquo;s not surprising that the XPS wins.&lt;/p&gt;
&lt;h2 id=&#34;linux-apps&#34;&gt;Linux Apps&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&#34;Screenshot 2018-12-23 at 16.54.29&#34; src=&#34;/static/img/2018/12/Screenshot-2018-12-23-at-16.54.29.png&#34;&gt; Just a typical day in Chrome OS running Firefox, VS Code, and Docker
I installed Firefox within five minutes of opening the Pixel Slate - because why not? Linux apps run &lt;em&gt;mostly&lt;/em&gt; well on the Slate. Setting them up is easy - just enable that option in settings. Installing apps is easy for someone experienced with the Linux command line, but harder for someone new to Linux. For example, on most Linux OS&amp;rsquo;s, you can double click a package file (such as a .deb file) and it installs. Not so on Chrome OS - you&amp;rsquo;ll need to use apt and dpkg to install programs like VS Code and Firefox.
Linux in Chrome OS (called Crostini) runs Debian Stretch in a container-based environment. That means it&amp;rsquo;s more efficient than a virtual machine and more secure that just executing Linux programs directly. It does add some inconveniences, such as having a separate file storage area (similar to Android).
Most things work just fine, but an exception was Docker. I followed the comments &lt;a href=&#34;https://bugs.chromium.org/p/chromium/issues/detail?id=860565&#34;&gt;here&lt;/a&gt; to get it working. I ran into another minor kink when installing gnome-terminal because no shortcut was created (every other app I installed did so and &amp;ldquo;just worked&amp;rdquo;). Crostini doesn&amp;rsquo;t support GPU acceleration at this time, so Steam gaming with the Slate isn&amp;rsquo;t going to be a great experience. Actual virtualization doesn&amp;rsquo;t work at all, although Wine does.
One perk of using the Slate as a developer is that you can develop Android apps and run them right on the device without an emulator. This does require enabling developer mode, which leads you to a rather annoying startup screen that must be bypassed by pressing CTRL-D or waiting 10 seconds. It&amp;rsquo;s actually really handy running Android apps directly in Chrome OS and not taking the typical performance hit from full virtualization.&lt;/p&gt;
&lt;h2 id=&#34;mobility-battery-life-and-other-features&#34;&gt;Mobility, Battery Life, and Other Features&lt;/h2&gt;
&lt;p&gt;The Slate weighs 1.6 lbs by itself; with the keyboard it&amp;rsquo;s 2.9 lbs. For comparison, the XPS 13 weighs 2.67 lbs - so the Slate as a laptop substitute is not a lighter option.
I get 4-6 hours of battery life on the XPS 13 when actually working. The Pixel Slate does better - more like 6-12 hours. (It&amp;rsquo;s hard to estimate because I&amp;rsquo;m typically not continuously coding/compiling things for more than 6 hours at a stretch.) This is no surprise given the lower power requirements of the Slate&amp;rsquo;s CPU.
The Slate easily goes into a suspend mode when inactive, just like an Android phone or tablet would. Ubuntu on the XPS is more finicky - it mostly works, but consumes more power when suspended and occasionally has glitches when waking. I would feel comfortable simply suspending the Slate when I step away from my work, whereas I often shut down my XPS 13 to avoid the issues just mentioned.
The Slate doesn&amp;rsquo;t have a headphone jack, and only has two USB-C ports. If I want to charge it, listen to music (through an adapter), and plug in a second monitor at the same time, then I need a USB-C dock. Google doesn&amp;rsquo;t provide much guidance on what adapters or docks are supported. I found USB-C to DisplayPort to work fine with a 4k monitor at 60hz, while a USB-HDMI adapter I use for my XPS didn&amp;rsquo;t work at all with the Slate. USB-C docks don&amp;rsquo;t support 4k at 60hz, and the ports appear not to be Thunderbolt-compatible. I found this whole connection process confusing and annoying - but in the end I got what I wanted using a USB-C dock (for power and audio) and a separate cable for DisplayPort.
The official Slate keyboard works as well as any device in this tablet-to-computer product class. It&amp;rsquo;s usable on your lap, but not good. It&amp;rsquo;s perfectly fine on a table. The round keys are a little odd, but I got used to them. It&amp;rsquo;s almost a full keyboard, including escape and F row keys - meaning I can use vim with it.
This may be a matter of personal taste, but I find the Slate too large and burdensome for reading an e-book. One advantage of the size, though, is that I can read full-size magazine articles without having to zoom or use the lite version.
The Slate&amp;rsquo;s magnets seem to be weaker than the Pixel-C&amp;rsquo;s, or maybe they&amp;rsquo;re the same but not strong enough for the increased weight. The Slate wouldn&amp;rsquo;t stay up when I tried sticking it to the fridge like I do with my Pixel-C. At the Slate&amp;rsquo;s vastly elevated price point, however, I probably wouldn&amp;rsquo;t trust it in the kitchen anyway!&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As a developer, I&amp;rsquo;d feel confident using the Pixel Slate as a replacement for my tablet and laptop. I&amp;rsquo;d still want a faster desktop with this set up and as a backup just in case Docker or something didn&amp;rsquo;t work right. As something I got from work and didn&amp;rsquo;t pay for myself - it&amp;rsquo;s great!
&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Great battery life&lt;/li&gt;
&lt;li&gt;Fast web performance&lt;/li&gt;
&lt;li&gt;A good way to run Linux with a solid, stable base OS that runs without glitches&lt;/li&gt;
&lt;li&gt;Running Android apps next to Linux apps all inside Chrome OS is really cool&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Expensive - I could buy both an XPS 13 &lt;em&gt;and&lt;/em&gt; a small tablet for less money&lt;/li&gt;
&lt;li&gt;CPU performance is slower than an &amp;ldquo;ultrabook&amp;rdquo;&lt;/li&gt;
&lt;li&gt;No headphone jack and not enough USB-C ports&lt;/li&gt;
&lt;/ul&gt;</description>
  </item>
  <item>
    <title>rxjs check as your type validation</title>
    <link>https://david.burke.nyc/blog/rxjs-check-as-your-type-validation/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/rxjs-check-as-your-type-validation/</guid>
    <pubDate>Wed, 11 Jul 2018 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;rxjs has a steep learning curve, but can do some really cool things. Let&amp;rsquo;s say you want an input form to do &amp;ldquo;as you type&amp;rdquo; async validation. Perhaps it&amp;rsquo;s checking if the username is taken or not. Another use case could be checking if some url is valid. I implemented this with ngrx-effects (after failing a lot!) and thought I would share.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  @Effect()
  asyncServerUrlCheck$ = this.store.select(fromAccount.getLoginForm).pipe(
    filter(form =&amp;gt; form.value.showUrl),
    distinctUntilChanged(
      (first, second) =&amp;gt; first.value.url === second.value.url
    ),
    switchMap(form =&amp;gt;
      concat(
        timer(300).pipe(
          map(
            () =&amp;gt; new StartAsyncValidationAction(form.controls.url.id, &amp;quot;exists&amp;quot;)
          )
        ),
        this.userService.checkUrl(form.value.url).pipe(
          map(() =&amp;gt; new ClearAsyncErrorAction(form.controls.url.id, &amp;quot;exists&amp;quot;)),
          catchError(() =&amp;gt; [
            new SetAsyncErrorAction(
              form.controls.url.id,
              &amp;quot;exists&amp;quot;,
              form.value.url
            )
          ])
        )
      )
    )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lots going on here and it sums up both my love and hate of rxjs. It&amp;rsquo;s unreadable garbage code until you understand it - then it&amp;rsquo;s fantastic. Let&amp;rsquo;s try to break this mess down.
First off - notice the asyncServerUrlCheck observable (All ngrx effects are just observables) is watching state instead of actions$. This is done because I&amp;rsquo;m watching the form field&amp;rsquo;s state instead of waiting for an action. Then I filter out changes that are not to the form field in question and I make sure it&amp;rsquo;s a real change.
Now the magic starts - next in my pipe is switchMap which is important because I want to cancel any previous observable.  If the user types google.co and then google.com I probably don&amp;rsquo;t want to check if google.co exists. switchMap throws out any previous work and start over. Of note - if I didn&amp;rsquo;t use switchMap I would see a LOT more network requests.
Next up is concat. concat is what is going to allow me to return multiple actions. Without it, I would just get the first start async validation and nothing else. concat is a static function and not a operator (Oh but it was an operator in rxjs 5 - which actually makes me hate rxjs a little because it&amp;rsquo;s so much mental overhead!). We&amp;rsquo;ll pass observables as parameters to concat. Our first concat observable is a timer. Timer is what implements the logic to wait until the user stops typing. Because we use it with switchMap earlier - it will get canceled if the user types something else! We could stop right now and have a start async validation action dispatch when the user stops typing. Cool. I do suggest trying this out piece by piece if you want to understand it rather than coping the entire snippet.
Now I need to add success and failure actions after the async call is made. I&amp;rsquo;ll add a second parameter to concat which is my service function call. The function will return an Observable (Once again remember that concat accepts a list of observables). I pipe this into a map and catchError. That logic should look familiar if you used effects before so I won&amp;rsquo;t go into detail.
&lt;img alt=&#34;Screenshot from 2018-07-11 10-01-35&#34; src=&#34;/static/img/2018/07/screenshot-from-2018-07-11-10-01-35.png&#34;&gt;
This is how it looks in redux devtools. I get lots of set value&amp;rsquo;s from each user character input. But I don&amp;rsquo;t get a start async validation for each one (meaning I don&amp;rsquo;t excessively check the server!). Then I get either set async error or clear async error (success) actions depending on if the server url is valid.
I&amp;rsquo;ve found this pattern hard to grok initially - but now is easy to apply elsewhere. Making async validation easy means I&amp;rsquo;ll be more likely to use it and give users a more interactive experience. Try it yourself at https://passit.io and download the chrome or firefox extension (The web version ask for server url). And if you aren&amp;rsquo;t a regular visitor to my blog - Passit is an open source, online password manager that my company built so please give it a try.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Forms with ngrx, NativeScript, and Angular</title>
    <link>https://david.burke.nyc/blog/forms-with-ngrx-nativescript-and-angular/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/forms-with-ngrx-nativescript-and-angular/</guid>
    <pubDate>Mon, 09 Apr 2018 12:00:00 +0000</pubDate>
<category>angular</category><category>NativeScript</category><category>ngrx</category><category>Passit</category>    <description>&lt;p&gt;There are many ways to make forms in Angular. There&amp;rsquo;s template driven, reactive, and the question of syncing with ngrx state or keeping the it local to the component. When making a NativeScript app it&amp;rsquo;s not always obvious how to reuse these forms. For example, template driven forms in Angular might use the dom&amp;rsquo;s &amp;ldquo;required&amp;rdquo; attribute.  NativeScript doesn&amp;rsquo;t have a dom or input component at all, so the required logic would have to be remade, perhaps using a required directive. Redux/ngrx driven forms offer a significant advantage when we have multiple platforms as ngrx is platform agnostic and we can perform the validation logic in the reducer instead of the component or a directive.
As a case study, I recently rewrote Passit&amp;rsquo;s &lt;a href=&#34;https://gitlab.com/passit/passit-frontend/blob/62493acc4d513b1d6eff8d059164e9e53be7e85e/src/app/account/login/login.reducer.ts&#34;&gt;login form&lt;/a&gt; with the fantastic &lt;a href=&#34;https://github.com/MrWolfZ/ngrx-forms&#34;&gt;ngrx-forms&lt;/a&gt; package. ngrx-forms takes care of common use cases while providing a blueprint and examples of how to make the state driven forms.
&lt;img alt=&#34;login&#34; src=&#34;/static/img/2018/04/login.png&#34;&gt; The validation logic is in the reducer, here two different platforms show the errors that get pulled from state
Using ngrx-forms on the web is straight-forward, just follow the &lt;a href=&#34;https://github.com/MrWolfZ/ngrx-forms/blob/master/docs/GETTING_STARTED.md&#34;&gt;docs&lt;/a&gt;. For NativeScript you&amp;rsquo;ll have to make a few changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is no &amp;ldquo;form&amp;rdquo; component in NativeScript, so you&amp;rsquo;ll have to manage isSubmitted yourself. You could modify the submit state in the reducer itself or in the &lt;a href=&#34;https://gitlab.com/passit/passit-mobile/blob/a681cb0ad00dbff592a1fbdb866569651798d697/app/account/login/login.component.ts#L27&#34;&gt;component&lt;/a&gt; with MarkAsSubmittedAction.&lt;/li&gt;
&lt;li&gt;ngrx-forms comes with directives to keep the form and ngrx data in sync. But these won&amp;rsquo;t work out of the box with NativeScript components. Here is a &lt;a href=&#34;https://gitlab.com/passit/passit-mobile/blob/a681cb0ad00dbff592a1fbdb866569651798d697/app/directives/input.directive.ts&#34;&gt;NgrxTextFieldViewAdapter&lt;/a&gt; for a TextInput. Just add the directive like [ngrxFormControlState]=&amp;rdquo;form.controls.yourField&amp;rdquo; and the TextInput state will sync with the form state, just like on the web.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now I can reuse all of my form validation logic in both platforms. The only difference is the presentational components for app and web.
Overall I think ngrx-forms offers a straight forward, redux friendly, and platform agnostic solution to forms. Please feel free to take a look at my Android &lt;a href=&#34;https://play.google.com/store/apps/details?id=com.burkesoftware.Passit&#34;&gt;preview release&lt;/a&gt; of Passit, the open source password manager. As I create more forms on both platforms I&amp;rsquo;m looking forward to having a single strategy for building them. Be sure to report bugs on &lt;a href=&#34;https://gitlab.com/passit/passit-mobile&#34;&gt;gitlab&lt;/a&gt;.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Does your password manager really need permissions to do anything ever?</title>
    <link>https://david.burke.nyc/blog/does-your-password-manager-really-need-permissions-to-do-anything-ever/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/does-your-password-manager-really-need-permissions-to-do-anything-ever/</guid>
    <pubDate>Sat, 17 Mar 2018 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;&lt;img alt=&#34;Screenshot from 2018-03-03 12-01-59&#34; src=&#34;/static/img/2018/03/screenshot-from-2018-03-03-12-01-59.png&#34;&gt;
Almost all password manager&amp;rsquo;s browser extensions have permission to &amp;ldquo;Read and change all your data on the websites you visit&amp;rdquo;. If that sounds scary, it&amp;rsquo;s because it is. That&amp;rsquo;s the &amp;ldquo;&lt;all\_urls&gt;&amp;rdquo; permission. It means the extension is allowed to execute arbitrary JavaScript at any time on any website without warning. Here&amp;rsquo;s some examples of what I could do if you installed an extension I made with &lt;all\_urls&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run a keylogger on every webpage you visit&lt;/li&gt;
&lt;li&gt;Inject extra ads into every website&lt;/li&gt;
&lt;li&gt;Run a password manager that autofills every login form even when you do not ask it to - this is in fact common. This includes &lt;a href=&#34;https://www.bleepingcomputer.com/news/security/web-trackers-exploit-flaw-in-browser-login-managers-to-steal-usernames/&#34;&gt;malicious forms&lt;/a&gt; that might have been injected into a victims website to steal your password.&lt;/li&gt;
&lt;li&gt;Run a password manager that checks each and every domain you ever visit and &lt;a href=&#34;https://labs.detectify.com/2016/07/27/how-i-made-lastpass-give-me-all-your-passwords/&#34;&gt;forget to sanitize the domain url&lt;/a&gt; making it vulnerable to code injection attacks that could lead to a rouge website capturing all of your passwords.&lt;/li&gt;
&lt;li&gt;If I&amp;rsquo;m feeling only slightly evil, I simply record each domain you ever visit and sell that data to advertisers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://passit.io/&#34;&gt;Passit&lt;/a&gt; does not require the &lt;all\_urls&gt; permission. This doesn&amp;rsquo;t make us invulnerable to all extension-based attacks, but it greatly mitigates them. Let&amp;rsquo;s consider some:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;I sell you out (perhaps to &lt;a href=&#34;https://arstechnica.com/information-technology/2014/01/malware-vendors-buy-chrome-extensions-to-send-adware-filled-updates/&#34;&gt;another company&lt;/a&gt;) and start making my extension serve ads or other garbage&amp;rdquo; - you&amp;rsquo;d get a notification about the increased permissions and hopefully you&amp;rsquo;d check our &lt;a href=&#34;https://passit.io/blog/&#34;&gt;blog&lt;/a&gt; to see why we want scary permissions.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;My sloppy code is vulnerable to JS injection attacks&amp;rdquo; - but because Passit doesn&amp;rsquo;t run until you invoke it, that probably only happens on websites you already know and trust at least somewhat.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;forget-security-vs-convenience&#34;&gt;Forget security vs convenience&lt;/h1&gt;
&lt;p&gt;(Sort of)
&lt;a href=&#34;https://passit.io/&#34;&gt;Passit&lt;/a&gt; has easy to use shortcuts for autofill, so you don&amp;rsquo;t give up much convenience. I personally don&amp;rsquo;t find pressing a shortcut key to log in to be a big burden, especially with such nice security gains. Our strategy also means Passit will never have Clippy-esque &amp;ldquo;Would you like to save this password??&amp;rdquo; forms because Passit will never bother you until you activate it.
&lt;img alt=&#34;&#34; src=&#34;/static/img/2018/03/out.gif&#34;&gt;
That said, Passit is positioned to be a web-based, easy-to-sync, and share/organization-friendly password manager. I think password managers like &lt;a href=&#34;https://www.passwordstore.org/&#34;&gt;pass&lt;/a&gt; still offer a benefit for personal use when you don&amp;rsquo;t care about sharing or autofill. The most secure password manager would be a piece of paper in a safe in a fort. At some point, we have to pick where we are comfortable between security and privacy vs convenience. I hope Passit makes an appealing choice that is nicer to use than programs like pass or KeePass while providing better security and privacy than LastPass or 1Password.
Try &lt;a href=&#34;https://passit.io/&#34;&gt;Passit&lt;/a&gt; out today. Use our free hosted service or run it yourself. If you like it, please star us on &lt;a href=&#34;http://gitlab.com/passit&#34;&gt;Gitlab&lt;/a&gt; and &lt;a href=&#34;https://gitlab.com/passit/passit-frontend&#34;&gt;report&lt;/a&gt; some feature requests or issues.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Review: Dell XPS 13 9370 Developer Edition</title>
    <link>https://david.burke.nyc/blog/review-dell-xps-13-9370-developer-edition/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/review-dell-xps-13-9370-developer-edition/</guid>
    <pubDate>Fri, 09 Feb 2018 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;As the owner of &lt;a href=&#34;https://burkesoftware.com/&#34;&gt;Burke Software and Consulting&lt;/a&gt; I get to play with a few more Linux laptops than I would as an individual. I recently picked up Dell&amp;rsquo;s latest XPS 13 (9370) Developer Edition. Here&amp;rsquo;s my review as a developer.&lt;/p&gt;
&lt;h1 id=&#34;comparing-laptops&#34;&gt;Comparing Laptops&lt;/h1&gt;
&lt;p&gt;I compared the current 9370 model with the 2016 9350 model. I also compared a couple benchmarks with the Galago Pro 2 from System76, which is another laptop with Linux preloaded.
Both XPS computers are top of the line with Intel i7 branded CPUs. The 9370 is an 8th generation Intel i7-8550U CPU while the 9350 is a 6th generation i7-6560U. The Galago Pro was configured with a i5 processor - so while it&amp;rsquo;s interesting to throw in it&amp;rsquo;s not a totally fair comparison.&lt;/p&gt;
&lt;h1 id=&#34;hardware-and-appearance&#34;&gt;Hardware and Appearance&lt;/h1&gt;
&lt;p&gt;The 9370 model is noticeably slimmer as seen in these photos. The other dimensions are the same. It&amp;rsquo;s noticeably a little lighter too. I tested it with a friend, having them close their eyes and pick the lighter laptop to make sure it wasn&amp;rsquo;t a placebo effect. The Dell USB-C charger is also a little bit smaller which is nice.
The camera is still unusable due to its placement. Dell moved it to the center in the 9370 model which is maybe a slight improvement, but all you will see is your fingers on the keyboard and up your nose with this camera. I can&amp;rsquo;t think of any circumstance in which I would use this horribly placed camera.&lt;/p&gt;
&lt;h1 id=&#34;benchmarks&#34;&gt;Benchmarks&lt;/h1&gt;
&lt;p&gt;I benchmarked some typical developer tasks. I tested by building the open source password manager &lt;a href=&#34;https://passit.io&#34;&gt;Passit&lt;/a&gt;. This has been my passion project for the past couple of years and if you aren&amp;rsquo;t using a password manager yet and like open source I encourage you to try it out (and give me feedback).
I tested a Webpack bundle from the &lt;a href=&#34;https://gitlab.com/passit/passit-frontend&#34;&gt;passit-frontend Gitlab repo&lt;/a&gt;. This runs angular-cli&amp;rsquo;s serve command.
&lt;strong&gt;yarn start (webpack)&lt;/strong&gt;9370: 11173ms
9350: 12322ms
Galago: 13945ms
It appears the 9370 is only slightly faster, saving about 1 full second. That is not impressive for a upgrade of two CPU generations.
&lt;strong&gt;Ubuntu Disk Utility Benchmark&lt;/strong&gt;Average Read Rate
9370: 2.7 GB/s
9350: 1.6 GB/s
Average Access Time (lower is better)
9370: 0.04 msec
9350: 0.22 msec
Looks like the 9370 has a faster SSD. That&amp;rsquo;s always a good thing. We can see how benchmarks look impressive but real world results don&amp;rsquo;t reflect it.
&lt;strong&gt;time tns run android&lt;/strong&gt;
9370: 1m 49s
9350: 2 m9s
This test uses the &lt;a href=&#34;https://gitlab.com/passit/passit-mobile&#34;&gt;passit-mobile repo&lt;/a&gt;. There&amp;rsquo;s a lot going on. It needs to compile the typescript into JavaScript, build the apk file, start an android emulator, and load the apk file on the device. I did my test using the Unix &amp;ldquo;time&amp;rdquo; command. I manually stopped it when I saw the Passit app fully loaded in the emulator. I like this test because it does SO much. Some of the Android compilers take advantage of multiple cores while the node based tools can use only one core. A big change on the  i7-8550U CPU is having 4 cores and 8 threads (previous comparable U series CPUs had 2 cores). We can see a decent speed boost on the new XPS here. Nothing ground shattering. If your workload is more multi core utilizing you might see bigger jumps in performance.
All tests were done on battery.&lt;/p&gt;
&lt;h1 id=&#34;battery&#34;&gt;Battery&lt;/h1&gt;
&lt;p&gt;I was pleased to see a good boost in battery life on the new XPS 9370 despite it having smaller battery and a higher resolution display of 3840 x 2160 (9350 topped out at 3200 x 1800). Battery testing is hard and your results will vary. In the type of work I do (web development, vim, browsing) I got about 4-5 hours on the 9350. I seem to get around 7-8 on the 9370. I&amp;rsquo;ve only been using it a couple days, so I&amp;rsquo;ll update this post if I find it varies. On both set ups I do not have powertop or TLP installed - I find they can make Linux too unstable for me.
If you opt for the lower screen resolution on any XPS model, you will get vastly better battery life. Unfortunately, Dell does not offer the lower resolution with 16GB of RAM, which made it not an option for me.&lt;/p&gt;
&lt;h1 id=&#34;gaming&#34;&gt;Gaming&lt;/h1&gt;
&lt;p&gt;The XPS 13 line is not for gaming so I&amp;rsquo;ll keep this short. The 9350 had an Intel Iris integrated GPU option while the 9370 offers only the less powerful UHD 620 integrated GPU. At first I was worried this would mean no more Cities Skylines for me - but that is not the case. Cities Skylines runs just fine on very low settings on the 9370. Dell says the heat dissipation is improved on their new laptop so it may be one reason for decent performance even without the better Iris GPU. If you want some light gaming, the XPS 13 dev edition is a solid choice.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The new Dell XPS developer edition is a modest improvement. I think the better battery life even on the higher end model is the biggest improvement. It&amp;rsquo;s probably not worth upgrading from the 9350 model but it might be from anything earlier. Performance gains are minimal, possibly due to Intel CPU&amp;rsquo;s having only minor performance upgrades the past couple years.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Building Nativescript apps on Gitlab CI</title>
    <link>https://david.burke.nyc/blog/building-nativescript-on-gitlab-ci/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/building-nativescript-on-gitlab-ci/</guid>
    <pubDate>Sat, 27 Jan 2018 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;I&amp;rsquo;m building a &lt;a href=&#34;https://passit.io&#34;&gt;Passit&lt;/a&gt; Android app through Gitlab CI. My goal is to produce a signed apk file in passit-mobile&amp;rsquo;s public &lt;a href=&#34;https://gitlab.com/passit/passit-mobile/&#34;&gt;Gitlab repo&lt;/a&gt; without exposing my signing key. This post will outline what I did and act as a starting point to anyone wishing to do the same. It should also be relevant for anyone using React Native or another Docker based CI solution.
&lt;img alt=&#34;Screenshot from 2018-01-27 18-31-52&#34; src=&#34;/static/img/2018/01/screenshot-from-2018-01-27-18-31-52.png&#34;&gt;
At a high level: In order to generate the APK file automatically in Gitlab CI, I need to build my project in Docker. That means I need the Android SDK and the Node environment for Nativescript in Docker. I also need to get my key file and password to the CI runner in a secure manner. Finally, I need to place the file somewhere where it can be downloaded.
Here&amp;rsquo;s my complete &lt;a href=&#34;https://gitlab.com/passit/passit-mobile/blob/master/.gitlab-ci.yml&#34;&gt;.gitlab-ci.yml&lt;/a&gt; file.&lt;/p&gt;
&lt;h1 id=&#34;local-docker&#34;&gt;Local Docker&lt;/h1&gt;
&lt;p&gt;Local docker isn&amp;rsquo;t CI! OK, but we need to run everything locally inside Docker first to make sure it works. It&amp;rsquo;s no fun waiting on slow CI when debugging. I&amp;rsquo;ll create a &lt;a href=&#34;https://gitlab.com/passit/passit-mobile/blob/master/Dockerfile&#34;&gt;Dockerfile&lt;/a&gt; for my project. I found runmymind/docker-android-sdk:ubuntu-standalone to be the most popular and still-updated Android SDK Docker image at the time of this writing. However, the image doesn&amp;rsquo;t have node installed, since Android is Java-based. So my Dockerfile is going to install node, Nativescript, other packages, and the Android platform version I want. Here are the relevant lines:&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# Installs Node.js
ENV NODE_VERSION 8.9.4
RUN cd &amp;amp;&amp;amp; \
 wget -q http://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz &amp;amp;&amp;amp; \
 tar -xzf node-v${NODE_VERSION}-linux-x64.tar.gz &amp;amp;&amp;amp; \
 mv node-v${NODE_VERSION}-linux-x64 /opt/node &amp;amp;&amp;amp; \
 rm node-v${NODE_VERSION}-linux-x64.tar.gz
ENV PATH ${PATH}:/opt/node/bin

# Installs nativescript
RUN npm install -g nativescript --unsafe-perm
RUN npm install nativescript --unsafe-perm
# installs android platform
RUN $ANDROID_HOME/tools/bin/sdkmanager &amp;quot;tools&amp;quot; &amp;quot;platform-tools&amp;quot; &amp;quot;platforms;android-25&amp;quot; &amp;quot;build-tools;25.0.2&amp;quot; &amp;quot;extras;android;m2repository&amp;quot; &amp;quot;extras;google;m2repository&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Neat. Notice the &amp;ndash;unsafe-perm in the Nativescript install. That&amp;rsquo;s right from the &lt;a href=&#34;https://docs.nativescript.org/start/ns-setup-linux#advanced-setup-steps&#34;&gt;Nativescript advanced install steps&lt;/a&gt;. At this time platform version (25) works with Nativescript, but you may need to change it.
I also made a tiny &lt;a href=&#34;https://gitlab.com/passit/passit-mobile/blob/master/docker-compose.yml&#34;&gt;docker compose file.&lt;/a&gt; I probably don&amp;rsquo;t need to but I like it&amp;rsquo;s easy to remember cli syntax. To build and run the docker image:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install Docker if you haven&amp;rsquo;t already&lt;/li&gt;
&lt;li&gt;docker-compose build&lt;/li&gt;
&lt;li&gt;docker-compose run &amp;ndash;rm android bash&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The last step is to get a bash terminal inside the container. I suggest trying a tns command to see if it works - it may not connect to any emulators nor devices, but it should be able to build.&lt;/p&gt;
&lt;h1 id=&#34;building&#34;&gt;Building&lt;/h1&gt;
&lt;p&gt;Before continuing, I suggest you read https://docs.nativescript.org/publishing/publishing-android-apps if you haven&amp;rsquo;t already.
Generally I build with webpack, aot, and snapshot but not uglify (can&amp;rsquo;t get it to work yet). This looks like:
&lt;code&gt;tns build android --release --bundle --env.snapshot --env.aot --key-store-path your-key.jks --key-store-password &amp;lt;your-password&amp;gt; --key-store-alias-password &amp;lt;your-password&amp;gt; --key-store-alias your-alias&lt;/code&gt;
I&amp;rsquo;d suggest ensuring this command works outside of Docker first, then within your Docker container, and finally on CI. The reason to use all of this and not just tns build Android is to make app performance and startup better. Nativescript + Angular is quite slow otherwise. If you aren&amp;rsquo;t using Angular, it will be a little different but you can still use snapshot.&lt;/p&gt;
&lt;h1 id=&#34;gitlab-ci&#34;&gt;Gitlab CI&lt;/h1&gt;
&lt;p&gt;At this point you have verified that you can build an APK file inside of Docker. Next up is the &lt;a href=&#34;https://gitlab.com/passit/passit-mobile/blob/master/.gitlab-ci.yml&#34;&gt;.gitlab-ci.yml&lt;/a&gt; file. I basically just mimic my local Docker file. You could actually use the same image if you wanted and use Docker in Docker. It seems a little simpler to me to keep them separate. There are a few extra commands I&amp;rsquo;ll call out.
&lt;code&gt;echo $ANDROID_KEY_BASE64 | base64 -d &amp;gt; key.jks&lt;/code&gt;
I need to get my key file into gitlab CI without committing to the public repo. I can use Gitlab CI&amp;rsquo;s &lt;a href=&#34;https://docs.gitlab.com/ce/ci/variables/README.html#protected-secret-variables&#34;&gt;protected secret variables&lt;/a&gt; for this. Unfortunately, Gitlab doesn&amp;rsquo;t support private files at this time. But I can base64 encode bytes - and what is a file if not a series of bytes?
&lt;code&gt;cat your-key.jks | base64 -w 0&lt;/code&gt;
This will convert your key file to base64 without newlines (which will be more gitlab CI variable friendly). Now paste this base64 into a secret and protected variable. I called mine ANDROID_KEY_BASE64. I also set my password for the key to ANDROID_KEY_PASSWORD (doesn&amp;rsquo;t need to be base64). Now I can regenerate my key file on the fly in the CI runner. Protected secret variables will only show up in a protected branch - and I can limit who has access to protected branches. So no one can get my info by, say, submitting a merge request with echo $ANDROID_KEY_PASSWORD.
Even if your repo is private, it&amp;rsquo;s not a bad idea to keep secrets hidden so that others can contribute without seeing them.
The last thing I&amp;rsquo;ll note is my artifacts section, which will store my APK file after each build. This is great for daily build users or if you want to just manually upload the APK to Google Play and other stores. Ensuring the app builds with production settings is also a fantastic test in itself.&lt;/p&gt;
&lt;h1 id=&#34;final-thoughts&#34;&gt;Final Thoughts&lt;/h1&gt;
&lt;p&gt;That&amp;rsquo;s it - fully automated APK builds. This method will not work for iOS, as Apple does not allow you to build iOS apps in non-Apple operating systems such as Linux. And Docker runs in Linux.
(Side rant: Screw you, Apple! I sure hope the U.S. gets an administration that cares about anti-trust again - then maybe your development process wouldn&amp;rsquo;t be two decades in the past. Why would you make a human build something when it could just be automated?)
Improvements and/or the next levels of this process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Running unit and integration tests. Unit testing comes with nativescript and integration testing could be done with Appium. You can actually run an Android emulator in Docker.&lt;/li&gt;
&lt;li&gt;Automating the deploy based on branch or tags to publish to any stores.&lt;/li&gt;
&lt;li&gt;It would be great if someone made a Nativescript app-building Docker image - that would let me simplify many steps here. I could imagine even tagging each supported Android platform version so the only steps to do per project would be sending over the key file, npm install, and tns build.&lt;/li&gt;
&lt;li&gt;You could try iOS builds by hooking up a physical Apple computer to gitlab CI - you can keep it in your garage right next to your horse and buggy!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Feel free to comment if any of these steps aren&amp;rsquo;t working right!&lt;/p&gt;</description>
  </item>
  <item>
    <title>Server side tracking with piwik and Django</title>
    <link>https://david.burke.nyc/blog/server-side-tracking-with-piwik-and-django/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/server-side-tracking-with-piwik-and-django/</guid>
    <pubDate>Sun, 26 Nov 2017 12:00:00 +0000</pubDate>
<category>django</category><category>Passit</category><category>piwik</category>    <description>&lt;p&gt;Business owners want to track usage to gain insights on how users actually use their sites and apps. However tracking can raise privacy concerns, lead to poor site performance, and raises security concerns by inviting third party javascript to run.
For &lt;a href=&#34;https://passit.io&#34;&gt;Passit&lt;/a&gt;, an open source password manager, we wanted to track how people use our app and view our passit.io marketing site. However we serve a privacy sensitive market. Letting a company like Google snoop on your password manager feels very wrong. Our solution is to use the open source and self hosted piwik analytics application with server side tracking.&lt;/p&gt;
&lt;h2 id=&#34;traditional-client-side-tracking-for-our-marketing-site&#34;&gt;Traditional client side tracking for our marketing site&lt;/h2&gt;
&lt;p&gt;passit.io uses the piwik javascript tracker. It runs on the same domain (piwik.passit.io) and doesn&amp;rsquo;t get flagged by Privacy Badger as a tracking tool. It won&amp;rsquo;t track your entire web history like Google Analytics or Facebook like buttons do.
&lt;a href=&#34;/static/img/2017/11/screenshot-from-2017-11-26-16-58-07.png&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://davidmburke.com/files/2017/11/Screenshot-from-2017-11-26-16-58-07-300x104.png&#34;&gt;&lt;/a&gt; Nice green 0 from privacy badger!
To respect privacy we can keep on the default piwik settings to anonomize ip addresses and respect the do not track header.&lt;/p&gt;
&lt;h2 id=&#34;server-side-tracking-for-apppassitio&#34;&gt;Server side tracking for app.passit.io&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;d like to have some idea of how people use our app as well. Sign ups, log ins, groups usage, ect. However injecting client side code feels wrong here. It would be a waste of your computer&amp;rsquo;s resources to track your movements to our piwik server and provides an attack vector. What if someone hijacked our piwik server and tried to inject random js into the passit app?
We can track usage of the app.passit.io api on the server side instead. We can simply track how many people use different api endpoints to get a good indication of user activity.&lt;/p&gt;
&lt;h3 id=&#34;django-and-piwik&#34;&gt;Django and piwik&lt;/h3&gt;
&lt;p&gt;Presenting &lt;a href=&#34;https://gitlab.com/burke-software/django-server-side-piwik&#34;&gt;django-server-side-piwik&lt;/a&gt;- a drop in Django app that uses middleware and Celery to record server side analytics. Let&amp;rsquo;s talk about how it&amp;rsquo;s built.
server_side_piwik uses the python &lt;a href=&#34;http://piwikapi.readthedocs.io/en/latest/index.html&#34;&gt;piwikapi&lt;/a&gt; package to track server side usage. Their quickstart section shows how. We can implement it as Django middleware. Every request will have some data serialized and sent to a celery task for further processing. This means our main request thread isn&amp;rsquo;t blocked and we don&amp;rsquo;t slow down the app just to run analytics.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;class PiwikMiddleware(object):
  &amp;quot;&amp;quot;&amp;quot; Record every request to piwik &amp;quot;&amp;quot;&amp;quot;
  def __init__(self, get_response):
  self.get_response = get_response

def __call__(self, request):
  response = self.get_response(request)

  SITE_ID = getattr(settings, &amp;#39;PIWIK_SITE_ID&amp;#39;, None)
  if SITE_ID:
    ip = get_ip(request)
    keys_to_serialize = [
      &amp;#39;HTTP_USER_AGENT&amp;#39;,
      &amp;#39;REMOTE_ADDR&amp;#39;,
      &amp;#39;HTTP_REFERER&amp;#39;,
      &amp;#39;HTTP_ACCEPT_LANGUAGE&amp;#39;,
      &amp;#39;SERVER_NAME&amp;#39;,
      &amp;#39;PATH_INFO&amp;#39;,
      &amp;#39;QUERY_STRING&amp;#39;,
    ]
    data = {
      &amp;#39;HTTPS&amp;#39;: request.is_secure() 
    }
    for key in keys_to_serialize:
      if key in request.META:
        data[key] = request.META[key]
    record_analytic.delay(data, ip)
  return response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now you can track usage from the backend which better respects user privacy. No javascript and no Google Analytics involved!
Feel free to check out the project on &lt;a href=&#34;https://gitlab.com/burke-software/django-server-side-piwik&#34;&gt;gitlab&lt;/a&gt; and let me know any comments or issues. &lt;a href=&#34;https://gitlab.com/passit&#34;&gt;Passit&amp;rsquo;s source&lt;/a&gt; is also on gitlab.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Using libsodium in Android and NativeScript.</title>
    <link>https://david.burke.nyc/blog/using-libsodium-in-android-and-nativescript/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/using-libsodium-in-android-and-nativescript/</guid>
    <pubDate>Tue, 20 Jun 2017 12:00:00 +0000</pubDate>
<category>Android</category><category>crypto</category><category>Java</category><category>libsodium</category><category>NativeScript</category>    <description>&lt;p&gt;&lt;a href=&#34;https://download.libsodium.org/doc/&#34;&gt;Libsodium&lt;/a&gt; is a fantastic crypto library, however it&amp;rsquo;s documentation can be lacking. Libsodium supports many languages via native wrappers and javascript via asm.js. I&amp;rsquo;ll document here how I got it working in Android both in Java and NativeScript. The target audience is someone who knows web development but not Android development.&lt;/p&gt;
&lt;h1 id=&#34;java-android&#34;&gt;Java + Android&lt;/h1&gt;
&lt;p&gt;We&amp;rsquo;ll use &lt;a href=&#34;https://github.com/joshjdevl/libsodium-jni&#34;&gt;libsodium-jni&lt;/a&gt;. The install instructions didn&amp;rsquo;t make sense to me at first because I&amp;rsquo;m not an experienced Android developer. There is no need to manually compile it - they publish builds (AAR files) to the Sonatype OSS repository. This is analogous to publishing packages to the NPM repository if you&amp;rsquo;re more familiar with javascript. Just like how there are a million ways to install javascript dependencies, there are a million and one ways to install java dependencies. This can make documentation appear confusing or contradictory. Here&amp;rsquo;s how I did it.
Make a new Android Studio project (I suggest starting simple instead of using your existing project). Edit app/build.gradle and in the dependencies section add:&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;compile &amp;#39;com.github.joshjdevl.libsodiumjni:libsodium-jni-aar:1.0.6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note the latest version might change. The format of that string is &amp;lsquo;groupId:artifactId:version&amp;rsquo; which you can find in libsodium-jni&amp;rsquo;s readme. That&amp;rsquo;s actually all you need to do to install it. gradle will find the package for you. Now try to run the app, you may get errors about android:allowBackup and the minimum android version supported.
I don&amp;rsquo;t understand why allowBackup could have any connection to libsodium - Native dev kind of sucks if you are used to something nicer like Python or Web development. We can fix it by editing app/src/main/AndroidManifest.xml and adding an attribute to the application tag.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tools:replace=&amp;quot;android:allowBackup&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you run again, it will complain about the &amp;ldquo;tools&amp;rdquo; attribute. Android XML configuration is fucking awful - go add this attribute to the manifest tag.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;xmlns:tools=&amp;quot;http://schemas.android.com/tools&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next we may need to fix the version number - this issue actually makes sense. Both your application and libsodium-jni declare the minimum android version supported. Just edit app/build.gradle and change minSdkVersion to whatever libsodium is asking for - at the time of this writing it&amp;rsquo;s minSdkVersion: 16.
It should now compile. Next let&amp;rsquo;s add some test code. Open MainActivity.java and we&amp;rsquo;ll generate a key pair to prove libsodium works. Add imports:&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;import org.libsodium.jni.SodiumConstants;
import org.libsodium.jni.crypto.Random;
import org.libsodium.jni.keys.KeyPair;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Modify the onCreate function to generate and print out the key pair&amp;rsquo;s public key:&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    byte[] seed = new Random().randomBytes(SodiumConstants.SECRETKEY_BYTES);
    KeyPair encryptionKeyPair = new KeyPair(seed);
    System.out.println(encryptionKeyPair.getPublicKey());
    setContentView(R.layout.activity_main);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now the app should run and the key should print out in logging (logcat). Success!&lt;/p&gt;
&lt;h1 id=&#34;nativescript&#34;&gt;Nativescript&lt;/h1&gt;
&lt;p&gt;Screw Java and Android XML and gradle configuration - let&amp;rsquo;s use javascript. A few years ago this might have been a sarcastic remark&amp;hellip;
Amazingly, you can generate typescript definitions using &lt;a href=&#34;https://github.com/NativeScript/android-dts-generator&#34;&gt;this&lt;/a&gt;. It works great - but I&amp;rsquo;d actually suggest getting a little familiar with the Java library in Java first. Java and javascript use very different conventions and the generated type definitions might not be perfect. Here&amp;rsquo;s a generated libsodium-jni type def. https://gitlab.com/snippets/1665527
To install make the same changes you made in the pure java app. The gradle and xml files are in platforms/android/. Drop in the libsodium type def file into the root of your project. You should now have code completion and type checking. Now let&amp;rsquo;s add that keypair generation test again to make sure it&amp;rsquo;s working.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;let rand = new org.libsodium.jni.crypto.Random().randomBytes(org.libsodium.jni.SodiumConstants.SECRETKEY_BYTES);
let key = new org.libsodium.jni.keys.KeyPair(rand);
console.log(key.getPublicKey());
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Looks weird right? But it should work. You probably want to make a wrapper around this in either javascript or Java. Mixing Java calls into your javascript will look strange and be hard to read and debug.
Hopefully you can run the project as usual with tns run android. You may want to build the project or run gradew clean first to avoid any cached builds. If you are successful you should again get the public key printed out in the log. Excellent! Now you can make a web/native hybrid app that uses libsodium&amp;rsquo;s cryptography and can run in the web and as an app!
&lt;a href=&#34;/static/img/2017/06/screenshot-from-2017-06-20-10-39-35.png&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;/static/img/2017/06/screenshot-from-2017-06-20-10-39-35.png&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;one-more-gotcha&#34;&gt;One more gotcha&lt;/h1&gt;
&lt;p&gt;If you have used libsodium in another language like Python or Javascript you may be spoiled with higher level function wrappers. For example in javascript libsodium you can run &amp;ldquo;let keypair = sodium.crypto_box_keypair();&amp;rdquo; to generate a keypair. libsodium-jni has very few high level wrappers so you&amp;rsquo;ll need to write C-like code in Java to mimic the low level functions. To make matters worse libsodium-jni isn&amp;rsquo;t documented. I found searching the code for unit tests to be the only way to find the right syntax. If you just start calling functions at will you&amp;rsquo;ll probably get a missing implementation error. If this happens you may want to see how it&amp;rsquo;s done in a unit test. &lt;a href=&#34;https://github.com/joshjdevl/libsodium-jni/blob/60926978ef54c066f9307f8a8cb4ce21295777a2/src/test/java/org/libsodium/jni/publickey/SealedBoxTest.java#L23&#34;&gt;Here&lt;/a&gt; is a link to the earlier key pair example but in a low level way. Now you are writing C like code in Java in Javascript. This is your life now.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Building a continuous integration workflow with gitlab and openshift 3</title>
    <link>https://david.burke.nyc/blog/building-a-continuous-integration-workflow-with-gitlab-and-openshift-3/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/building-a-continuous-integration-workflow-with-gitlab-and-openshift-3/</guid>
    <pubDate>Fri, 01 Apr 2016 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;In this post I&amp;rsquo;ll go over building and testing a Docker image with gitlab CI and then pushing that image to Openshift 3. It should be somewhat helpful for people using other Docker solutions like Kubernetes too or CI solutions like Jenkins. I&amp;rsquo;m using Django for the project with some front end assets built in node.
Our goal is to have one docker environment used in development, CI, staging, and production. We&amp;rsquo;ll avoid repeating ourselves with image building.
At a high level my workflow looks like
&lt;a href=&#34;http://davidmburke.com/2016/04/01/building-a-continuous-integration-workflow-with-gitlab-and-openshift-3/screenshot-from-2016-04-01-15-12-12/&#34;&gt;&lt;img alt=&#34;Screenshot from 2016-04-01 15-12-12&#34; src=&#34;/static/img/2016/04/screenshot-from-2016-04-01-15-12-12.png&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;local-development&#34;&gt;Local development&lt;/h1&gt;
&lt;p&gt;All local development happens with docker compose. There is &lt;a href=&#34;https://docs.docker.com/compose/django/&#34;&gt;plenty&lt;/a&gt; of info on the matter so I&amp;rsquo;ll skip most of this. I will point out that I want to use the same python based docker image for development and later in production.&lt;/p&gt;
&lt;h1 id=&#34;continuous-integration&#34;&gt;Continuous Integration&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;m using gitlab and &lt;a href=&#34;https://gitlab.com/gitlab-org/gitlab-ci-multi-runner&#34;&gt;gitlab CI runner&lt;/a&gt; to do testing and build a docker image. Gitlab has some &lt;a href=&#34;http://doc.gitlab.com/ce/ci/docker/using_docker_build.html&#34;&gt;docs&lt;/a&gt; on how to build a docker image. The choices are shell and docker-in-docker. I found docker-in-docker to be slow, complex, and error prone. In theory it would be better since the environments are more isolated.&lt;/p&gt;
&lt;h2 id=&#34;gitlab-ci-building-images-with-shell-executor&#34;&gt;Gitlab CI building images with shell executor&lt;/h2&gt;
&lt;p&gt;Here is my full &lt;a href=&#34;http://pastebin.com/maGgeeW2&#34;&gt;.gitlab-ci.yml file&lt;/a&gt; for reference.
The goal here is to build the image, run unit tests, deploy on success, and always clean up. I had to run gitlab ci runner as root otherwise I would get permission errors. :(
In a non trivial CI system, shell can get messy too. We need to be concerned about building too many images and filling all disk space, exhausting the number of docker network subnet pools, and ensuring concurrency works (if you need that).
Disk space - I suggest using a service that lets you attach a large volume that is formatted with an lot of inodes and using Docker&amp;rsquo;s overlayfs storage engine. I used AWS&amp;rsquo;s EC2 with a 120gb mount for /var/docker. See this &lt;a href=&#34;http://blog.cloud66.com/docker-with-overlayfs-first-impression/&#34;&gt;blog post&lt;/a&gt; for details. Pay attention to the part where you define inodes. I went with 16568256.
Docker clean up - Gitlab has a docker image that can help clean up docker images and containers for you &lt;a href=&#34;https://gitlab.com/gitlab-org/gitlab-runner-docker-cleanup&#34;&gt;here&lt;/a&gt;. I&amp;rsquo;d also consider restarting the server at night and running your own clean up scripts too. I also place a CI cleanup stage like&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;stages:
  - build
  - test
  - deploy
  - clean

...
variables:
  PROJECT_NAME: myproject$CI_BUILD_REF
  COMPOSE: docker-compose -p myproject$CI_BUILD_REF

...
clean_docker:
  stage: clean
  when: always
  script:
    - $COMPOSE stop
    - $COMPOSE down
    - $COMPOSE rm -f
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I&amp;rsquo;ve been using docker since 1.0 and I&amp;rsquo;m still always amazed by how it finds new ways of breaking itself. You may need to add your own hacks to seek and destroy docker images and containers that will want to build up forever.
The $CI_BUILD_REF is to ensure each docker image is unique - this allows us to run multiple builds and have some certainty the image being tested is the one being pushed to docker hub.
The test stages are rather django/node specific. Just place whatever code needs to execute to run tests here. If it gets a success exit code gitlab CI will know it passed.
Pushing to docker hub - I&amp;rsquo;m tagging my tested image, pushing it to docker hub, and running a webhook to notify openshift to automatically pull the image and deploy it to staging.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;deploy_staging:
  stage: deploy
  only:
   - qa
  script:
   - echo &amp;amp;quot;Tag and push ${PROJECT_NAME}_web&amp;amp;quot;
    - docker tag ${PROJECT_NAME}_web ${IMAGE_NAME}qa
    - docker push ${IMAGE_NAME}:qa
    - &amp;amp;quot;./bin/send-deploy-webhook.sh qa $DEPLOY_WEBHOOK_STAGING&amp;amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice how I&amp;rsquo;m only running this on the qa branch and that I&amp;rsquo;m tagging the image as &amp;ldquo;qa&amp;rdquo;. I&amp;rsquo;m using docker tags so that I can have one image that has different development stages - dev, staging, and production.&lt;/p&gt;
&lt;h1 id=&#34;openshift-with-docker-build-strategy&#34;&gt;Openshift with docker build strategy&lt;/h1&gt;
&lt;p&gt;Openshift lets you build using a source to image strategy or docker. Source to image would mean rebuilding a docker image - which we already did in CI. So let&amp;rsquo;s not use that. The docker strategy was a bit confusing to me however. I ended up having a very minimal build stage using Openshift&amp;rsquo;s docker build strategy. Here is a snippet from my build yaml.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  source:
    type: Dockerfile
    dockerfile: &amp;amp;quot;FROM thelab/tsi-cocoon:devnRUN ./manage.py collectstatic --noinput&amp;amp;quot;
  strategy:
    type: Docker
    dockerStrategy:
      from:
        kind: DockerImage
        name: &amp;#39;docker.io/user/image:dev&amp;#39;
      pullSecret:
        name: dockerhub
      forcePull: true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice the source type Dockerfile with a VERY minimal inline dockerfile that just gets the right image and collects static (Django specific - this could really just be FROM image-name)
The strategy is set to type: Docker and includes my docker image and the &amp;ldquo;secret&amp;rdquo; needed to pull the image from my private repo. Note that if you must specify the full docker registry (docker.io/ect) or else it will not work with a private registry. You need to add the secret using &lt;code&gt;oc secrets new dockerhub .dockercfg=dockercfg&lt;/code&gt; where dockercfg is the file that might be under ~/.dockercfg.
forcePull is set to true so that openshift does a docker pull each time.
You&amp;rsquo;ll need to define deployment, services, ect in openshift - but I include that in the scope of this post. I switched a source to image build to docker based without having to touch anything else.
That&amp;rsquo;s it - the same docker image you used with compose locally should be on openshift. I set up a workflow where git commits on specific branches automatically deploy on openshift staging environments. Then I manually trigger the production deploy using the same image as staging.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Extending Openshift&#39;s source to image</title>
    <link>https://david.burke.nyc/blog/extending-openshifts-source-to-image/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/extending-openshifts-source-to-image/</guid>
    <pubDate>Wed, 02 Mar 2016 12:00:00 +0000</pubDate>
<category>docker</category><category>openshift</category>    <description>&lt;p&gt;Openshift 3 introduces a new concept &amp;ldquo;source to image&amp;rdquo; or s2i. It&amp;rsquo;s a way to create a Docker image out of some source code and a Docker image - for example a python s2i image. It makes Openshift 3&amp;rsquo;s docker based workflow feel more like Openshift 2 or Heroku.
&lt;img alt=&#34;&#34; src=&#34;http://blog.octo.com/wp-content/uploads/2015/07/Openshift3-STI-Build-1024x430.png&#34;&gt; Image from http://blog.octo.com/en/openshift-3-private-paas-with-docker/
One problem I ran into using s2i-python was a lack of binary packages installed that are needed to build things like Pillow. For this we need to extend the base image to add what we want. You can see the end result on &lt;a href=&#34;https://github.com/thelabnyc/s2i-python&#34;&gt;github&lt;/a&gt;.
Let&amp;rsquo;s review the Dockerfile I build. We start by extending centos&amp;rsquo;s s2i image. Note I&amp;rsquo;m using Python 3.4. You can review the upstream project &lt;a href=&#34;https://github.com/openshift/sti-python/&#34;&gt;here&lt;/a&gt; to see what I&amp;rsquo;m extending.
&lt;code&gt;FROM centos/python-34-centos7&lt;/code&gt;
Next I&amp;rsquo;m mimicking how centos installs packages from the upstream file. &lt;a href=&#34;https://github.com/openshift/sti-python/blob/1d68235be8c5afdb827e6547bcd9e95a346cbd6d/3.4/Dockerfile#L18&#34;&gt;Upstream&lt;/a&gt; and &lt;a href=&#34;https://github.com/thelabnyc/s2i-python/blob/5ebc6b8d2b5556527f8772d3c64d6f8a01ff02f3/Dockerfile#L5&#34;&gt;my version&lt;/a&gt;
You can see I installed postgresql client tools and node too which I need. I also install a couple pip packages I know I&amp;rsquo;ll need on all my python projects - this is done just to speed up the build time. Do as much or as little as you want.
Next I&amp;rsquo;ll post it on Docker hub with automated builds. See &lt;a href=&#34;https://hub.docker.com/r/thelab/s2i-python&#34;&gt;here&lt;/a&gt;. I added centos/python-34-centos7 as a linked repository. This way my docker image builds any time the upstream image builds too - ensuring I get security updates.
&lt;a href=&#34;http://davidmburke.com/2016/03/02/extending-openshifts-source-to-image/screenshot-from-2016-03-02-15-40-55/&#34;&gt;&lt;img alt=&#34;Screenshot from 2016-03-02 15-40-55&#34; src=&#34;/static/img/2016/03/screenshot-from-2016-03-02-15-40-55.png&#34;&gt;&lt;/a&gt;
Finally I just use the image as I would the original s2i python in openshift. I can add it as I would any image with &lt;code&gt;oc import-image s2i-python --from=&#34;thelab/s2i-python:latest&#34; --confirm&lt;/code&gt;
Next post I&amp;rsquo;ll describe how to reuse a customized s2i python image on local development with Docker compose.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Finding near locations with GeoDjango and Postgis Part II</title>
    <link>https://david.burke.nyc/blog/finding-near-locations-with-geodjango-and-postgis-part-ii/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/finding-near-locations-with-geodjango-and-postgis-part-ii/</guid>
    <pubDate>Fri, 02 Oct 2015 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;This is a continuation of &lt;a href=&#34;http://davidmburke.com/2015/10/01/finding-near-locations-with-geodjango-and-postgis-part-i/&#34;&gt;part I&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&#34;converting-phrases-into-coordinates&#34;&gt;Converting phrases into coordinates&lt;/h1&gt;
&lt;p&gt;Let&amp;rsquo;s test out Nominatim - the Open Street Maps tool to search their map data. We basically want whatever the user types in to turn into map coordinates.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;from geopy.geocoders import Nominatim
from django.contrib.gis.geos import Point

def words_to_point(q):
    geolocator = Nominatim()
    location = geolocator.geocode(q)
    point = Point(location.longitude, location.latitude)
    return point
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can take vague statements like New York and get the coordinates of New York City. So easy!&lt;/p&gt;
&lt;h1 id=&#34;putting-things-together&#34;&gt;Putting things together&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;ll use Django Rest Framework to make an api where we can query this data and return json. I&amp;rsquo;m leaving out some scaffolding code - so make sure you are familiar with Django Rest Framework first - or just make your own view without it.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;class LocationViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    &amp;quot;&amp;quot;&amp;quot; Searchable list of locations

    GET params:

    - q - Location search paramater (New York or 10001)
    - distance - Distance away. Defaults to 50
    &amp;quot;&amp;quot;&amp;quot;
    serializer_class = LocationSerializer
    queryset = Location.objects.all()

    def get_queryset(self, *args, **kwargs):
        queryset = self.queryset
        q = self.request.GET.get(&amp;#39;q&amp;#39;)
        distance = self.request.GET.get(&amp;#39;distance&amp;#39;, 50)
        if q:
            point = words_to_point(q)
            distance = D(mi=distance)
            queryset = queryset.filter(point__distance_lte=(point, distance))
        return queryset
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;My central park location shows when I search for New York, but not London. Cool.
&lt;a href=&#34;/static/img/2015/10/screenshot-from-2015-10-02-14-55-45.png&#34;&gt;&lt;img alt=&#34;Screenshot from 2015-10-02 14-55-45&#34; src=&#34;/static/img/2015/10/screenshot-from-2015-10-02-14-55-45.png&#34;&gt;&lt;/a&gt;
At this point we have a usable backend. Next steps would be to pick a client side solution to present this data. I might post more on that later.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Finding near locations with GeoDjango and Postgis Part I</title>
    <link>https://david.burke.nyc/blog/finding-near-locations-with-geodjango-and-postgis-part-i/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/finding-near-locations-with-geodjango-and-postgis-part-i/</guid>
    <pubDate>Thu, 01 Oct 2015 12:00:00 +0000</pubDate>
<category>django</category><category>GIS</category>    <description>&lt;p&gt;With GeoDjango we can find places in proximity to other places - this is very useful for things like a store locator. Let&amp;rsquo;s use a store locater as an example. Our store locator needs to be able to read in messy user input (zip, address, city, some combination). Then, locate any stores we have nearby.&lt;/p&gt;
&lt;h1 id=&#34;general-concept-and-theory&#34;&gt;General concept and theory&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;/static/img/2015/10/screenshot-from-2015-10-01-17-16-31.png&#34;&gt;&lt;img alt=&#34;Screenshot from 2015-10-01 17-16-31&#34; src=&#34;/static/img/2015/10/screenshot-from-2015-10-01-17-16-31.png&#34;&gt;&lt;/a&gt;
We have two problems to solve. One is to turn messy address input into a point on the globe. Then we need a way to query this point against other known points and determine which locations are close.&lt;/p&gt;
&lt;h1 id=&#34;set-up-known-locations&#34;&gt;Set up known locations&lt;/h1&gt;
&lt;p&gt;Before we can really begin we need to set up GeoDjango. You can &lt;a href=&#34;https://docs.djangoproject.com/en/1.8/ref/contrib/gis/install/&#34;&gt;read the docs&lt;/a&gt; or use &lt;a href=&#34;https://gist.github.com/bufke/a6a9ba076a761be13c81&#34;&gt;docker-compose&lt;/a&gt;.
It&amp;rsquo;s still a good idea to read the &lt;a href=&#34;https://docs.djangoproject.com/en/1.8/ref/contrib/gis/tutorial/&#34;&gt;tutorial&lt;/a&gt; even if you use docker.
Let&amp;rsquo;s add a location. Something like:&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;class Location(models.Model):
    name = models.CharField(max_length=70)
    point = models.PointField()

    objects = models.GeoManager()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A PointField stores a point on the map. Because Earth is not flat we can&amp;rsquo;t use simple X, Y coordinates. Luckily you can almost think of Latitude and Longitude as X, Y. GeoDjango defaults to this. It&amp;rsquo;s also easy to get Latitude and Longitude from places like Google Maps. So if we want - we can ignore the complexities of mapping coordinates on Earth. Or you can read up on &lt;a href=&#34;https://en.wikipedia.org/wiki/SRID&#34;&gt;SRID&lt;/a&gt; if you want to learn more.
At this point we can start creating locations with points - but for ease of use add GeoModelAdmin to Django Admin to use Open Street Maps to set points.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;from django.contrib import admin
from django.contrib.gis.admin import GeoModelAdmin
from .models import Location

@admin.register(Location)
class LocationAdmin(GeoModelAdmin):
    pass
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&#34;/static/img/2015/10/screenshot-from-2015-10-01-17-31-54.png&#34;&gt;&lt;img alt=&#34;Screenshot from 2015-10-01 17-31-54&#34; src=&#34;/static/img/2015/10/screenshot-from-2015-10-01-17-31-54.png&#34;&gt;&lt;/a&gt;
Wow! We&amp;rsquo;re doing GIS!
Add a few locations. If you want to get their coordinates just type &lt;code&gt;location.point.x&lt;/code&gt; (or y).&lt;/p&gt;
&lt;h1 id=&#34;querying-for-distance&#34;&gt;Querying for distance.&lt;/h1&gt;
&lt;p&gt;Django has some &lt;a href=&#34;https://docs.djangoproject.com/en/1.8/ref/contrib/gis/db-api/#distance-queries&#34;&gt;docs&lt;/a&gt; for this. Basically make a new point. Then query distance. Like this:&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;from django.contrib.gis.geos import fromstr
from django.contrib.gis.measure import D
from .models import Location

geom = fromstr(&amp;#39;POINT(-73 40)&amp;#39;)
Location.objects.filter(point__distance_lte=(geom, D(m=10000)))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;m is meters - you can pass all sorts of things though. The result should be a queryset of Locations that are near our &amp;ldquo;geom&amp;rdquo; location.
Already we can find locations near other locations or arbitrary points! In Part II I&amp;rsquo;ll explain how to use Open Street Maps to turn a fuzzy query like &amp;ldquo;New York&amp;rdquo; into a point. And from there we can make a store locator!&lt;/p&gt;</description>
  </item>
  <item>
    <title>Review of Tutanota</title>
    <link>https://david.burke.nyc/blog/review-of-tutanota/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/review-of-tutanota/</guid>
    <pubDate>Sun, 27 Sep 2015 12:00:00 +0000</pubDate>
<category>email</category><category>review</category>    <description>&lt;p&gt;Tutanota is an open source email provider. It features easy to use end to end encryption. It&amp;rsquo;s notable as a modern, libre, and cheap hosted email provider.&lt;/p&gt;
&lt;h1 id=&#34;why-not-gmail&#34;&gt;Why not gmail&lt;/h1&gt;
&lt;p&gt;Gmail is easily the best email provider. It&amp;rsquo;s light years ahead of any open source system. It&amp;rsquo;s gratis for individual, education, nonprofit, and small business use. That makes it hard to compete with. However it&amp;rsquo;s also a giant proprietary service directing a huge portion of your communication to the world. As a free software advocate it&amp;rsquo;s hard to feel good about using Google services - especially ones as critical as communication. Google also took concerning steps to remove interoperability in it&amp;rsquo;s chat service by removing XMPP in the Google Hangouts.
In looking at alternatives I want something hosted (I do not want to deal with tech problems at home for something as important as email). I&amp;rsquo;d like something cheap - because even cheap is more expensive that Gmail. I also want something modern looking and easy to use.&lt;/p&gt;
&lt;h1 id=&#34;the-good&#34;&gt;The Good&lt;/h1&gt;
&lt;p&gt;Tutanota&amp;rsquo;s big feature is security. It&amp;rsquo;s not hosted in the United States. You can encrypt emails by clicking a button. It doesn&amp;rsquo;t sell your information to ad networks. It doesn&amp;rsquo;t leak your personal information when such ad networks or government agencies get hacked.
Tutanota has a modern web interface with native mobile apps (web wrapper, but acceptable). It&amp;rsquo;s minimalist but that isn&amp;rsquo;t necessarily bad.
&lt;a href=&#34;/static/img/2015/09/screenshot-from-2015-09-27-15-29-41.png&#34;&gt;&lt;img alt=&#34;Screenshot from 2015-09-27 15-29-41&#34; src=&#34;/static/img/2015/09/screenshot-from-2015-09-27-15-29-41.png&#34;&gt;&lt;/a&gt;
Tutanota is easy to use. You can use their hosted version if you trust them and don&amp;rsquo;t want to host yourself. Setting up a custom domain was pretty easy. Aliases are no problem. It supports multiple users too (but I haven&amp;rsquo;t tested this out yet).
It&amp;rsquo;s cheap at 1€ per month per user for the premium account. It also has a free tier if you can go without a custom domain.&lt;/p&gt;
&lt;h1 id=&#34;the-bad&#34;&gt;The Bad&lt;/h1&gt;
&lt;p&gt;Tutanota doesn&amp;rsquo;t support IMAP. That&amp;rsquo;s pretty annoying. I&amp;rsquo;d be highly annoyed if there was any decent open source email client - but as there is not I can forgive it. IMAP wouldn&amp;rsquo;t work with their security model. I &lt;a href=&#34;https://tutanota.uservoice.com/forums/237921-general/suggestions/9249942-imap-smtp-access&#34;&gt;wish&lt;/a&gt; they had it as an option with a big warning about security.
It lacks a lot of features you expect in email. As I said Gmail is way ahead here. No magic category filtering. No filtering rules. No contact book integration. Not even keyboard shortcuts.
Tutanota only recently open sourced it&amp;rsquo;s code. It needs to do more to promote a developer community. A public issue tracker would be nice. So would a contribution guide. Right now they only offer a user voice page that is more consumer focused. I left my &lt;a href=&#34;https://tutanota.uservoice.com/forums/237921-general/suggestions/9945159-explain-how-to-contribute-code&#34;&gt;feedback&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Tutanota is a great option if you want an open source email provider - but only if your feature requirements are minimal. It has great potential as they add more features. The world needs an easy way to send private messages and right now such privacy is a luxury for those very few who understand PGP encryption and can set up services themselves. I applaud anyone trying to make this easier.&lt;/p&gt;</description>
  </item>
  <item>
    <title>Mistakes to make when starting a business (with Python)</title>
    <link>https://david.burke.nyc/blog/mistakes-to-make-when-starting-a-business-with-python/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/mistakes-to-make-when-starting-a-business-with-python/</guid>
    <pubDate>Sat, 18 Jul 2015 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;I gave this talk today. Here are the &lt;a href=&#34;https://www.dropbox.com/s/i0kjtn0hq17am6h/mistakes-python.odp?dl=0&#34;&gt;slides&lt;/a&gt;. Feel free to reach out to me david at burkesoftware . com&lt;/p&gt;</description>
  </item>
  <item>
    <title>Building an api for django activity stream with Generic Foreign Keys</title>
    <link>https://david.burke.nyc/blog/building-an-api-for-django-activity-stream-with-generic-foreign-keys/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/building-an-api-for-django-activity-stream-with-generic-foreign-keys/</guid>
    <pubDate>Wed, 08 Jul 2015 12:00:00 +0000</pubDate>
<category>django</category>    <description>&lt;p&gt;I wanted to build a django-rest-framework api for interacting with django-activity-stream. Activity stream uses Generic Foreign Keys heavily which aren&amp;rsquo;t naturally supported. We can however reuse existing serializers and nest the data conditionally.
Here is a ModelSerializer for activity steam&amp;rsquo;s Action model.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;from rest_framework import serializers
from actstream.models import Action
from myapp.models import ThingA, ThingB
from myapp.serializers import ThingASerializer, ThingBSerializer

class GenericRelatedField(serializers.Field):
    def to_representation(self, value):
        if isinstance(value, ThingA):
            return ThingASerializer(value).data
        if isinstance(value, ThingB):
            return ThingBSerializer(value).data
        # Not found - return string.
        return str(value)

class ActionSerializer(serializers.ModelSerializer):
    actor = GenericRelatedField(read_only=True)
    target = GenericRelatedField(read_only=True)
    action_object = GenericRelatedField(read_only=True)

    class Meta:
        model = Action
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;GenericRelatedField will check if the value is an instance of a known Model and assign it the appropriate serializer.
Next we can use a viewset for displaying Actions. Since activity stream uses querysets it&amp;rsquo;s pretty simple to integrate with a ModelViewSet. In my case I&amp;rsquo;m checking for a get parameter to determine whether we want all actions, actions of people the logged in user follows, or actions of the user. I added some filters on action and target content type too.&lt;/p&gt;
&lt;div class=&#34;codehilite&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;from rest_framework import viewsets
from actstream.models import user_stream, Action
from .serializers import ActionSerializer

class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = ActionSerializer

    def get_queryset(self):
        following = self.request.GET.get(&amp;#39;following&amp;#39;)
        if following and following != &amp;#39;false&amp;#39; and following != &amp;#39;0&amp;#39;:
            if following == &amp;#39;myself&amp;#39;:
                qs = user_stream(self.request.user, with_user_activity=True)
                return qs.filter(actor_object_id=self.request.user.id)
            else:  # Everyone else but me
                return user_stream(self.request.user)
        return Action.objects.all()

    filter_fields = (
        &amp;#39;actor_content_type&amp;#39;, &amp;#39;actor_content_type__model&amp;#39;,
        &amp;#39;target_content_type&amp;#39;, &amp;#39;target_content_type__model&amp;#39;,
    )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here&amp;rsquo;s the end result, lots of nested data.
&lt;a href=&#34;/static/img/2015/07/screenshot-from-2015-07-08-174459.png&#34;&gt;&lt;img alt=&#34;Screenshot from 2015-07-08 17:44:59&#34; src=&#34;/static/img/2015/07/screenshot-from-2015-07-08-174459.png&#34;&gt;&lt;/a&gt;&lt;/p&gt;</description>
  </item>
  <item>
    <title>Open source chat</title>
    <link>https://david.burke.nyc/blog/open-source-chat/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/open-source-chat/</guid>
    <pubDate>Thu, 04 Jun 2015 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;Web based chatting services like Hipchat and Slack are catching on these days. Such services are proprietary which severely limits your freedom to extend them and gives control over communication to a for profit company and probably the NSA. What free alternatives are out there? We have good old IRC and XMPP servers. But these are typically a no go for non technical folks and time consuming to set up. Let&amp;rsquo;s review web based offerings starting with my favorite.&lt;/p&gt;
&lt;h1 id=&#34;lets-chat&#34;&gt;Let&amp;rsquo;s Chat&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;/static/img/2015/06/screenshot-from-2015-06-03-231331.png&#34;&gt;&lt;img alt=&#34;Screenshot from 2015-06-03 23:13:31&#34; src=&#34;/static/img/2015/06/screenshot-from-2015-06-03-231331.png&#34;&gt;&lt;/a&gt;
&lt;a href=&#34;https://github.com/sdelements/lets-chat&#34;&gt;Let&amp;rsquo;s chat&lt;/a&gt; is based on node and mongodb. It features a nice web interface and some very basic xmpp support. It is not federated sadly so you can&amp;rsquo;t talk with xmpp users outside your server. It has no hosted offering. Installing it was the easiest of the options I looked at yet very buggy and annoying. This is why Slack is winning folks. Anyway here&amp;rsquo;s how I ran it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install from git. Do not use docker due to &lt;a href=&#34;https://github.com/sdelements/lets-chat/issues/465&#34;&gt;this&lt;/a&gt; bug.&lt;/li&gt;
&lt;li&gt;Enable xmpp in the settings. You must set the host setting due to &lt;a href=&#34;https://github.com/sdelements/lets-chat/issues/302&#34;&gt;this&lt;/a&gt; bug. Here is my &lt;a href=&#34;http://pastebin.com/ukBxVj57&#34;&gt;settings&lt;/a&gt;. I also had to enable TLS to get xmpp to work.&lt;/li&gt;
&lt;li&gt;I use the hubot connector for fun stuff like animated gifs and scripting - which makes it more comparable to slack which offers a lot of integrations.&lt;/li&gt;
&lt;li&gt;For Android I use &lt;a href=&#34;https://play.google.com/store/apps/details?id=eu.siacs.conversations&amp;amp;hl=en&#34;&gt;Conversations&lt;/a&gt; which is an amazing xmpp client.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While I really hated installing Let&amp;rsquo;s Chat due to all the bugs - I&amp;rsquo;m happy with it now that it&amp;rsquo;s running. While I speak a bit badly about all the bugs I really appreciate the work put into it.&lt;/p&gt;
&lt;h1 id=&#34;kaiwa&#34;&gt;Kaiwa&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;getkaiwa.com&#34;&gt;Kaiwa&lt;/a&gt; is a web front end for an XMPP server. The huge benefit of this is you get a full XMPP server like Prosody which supports many of the more modern XMPP conventions (XEP&amp;rsquo;s). Federation is REALLY amazing and awesome except you will never use it because no one uses XMPP. In theory it would let you talk with people on other servers.
Kaiwa has some heavy requirements of a web server, ldap server, database, and xmpp server. ldap is a deal breaker for me. I would prefer storing user data by marking rusty nails that I must arrange myself in a garden of broken glass - because that would be far more pleasant than writing ldif files. The default kaiwa would lose all data when restarting docker even after mounting (looks to be an issue with the ldap container). I just gave up. Kaiwa also has no way to search history which is pretty important to me.&lt;/p&gt;
&lt;h1 id=&#34;rocketchat&#34;&gt;RocketChat&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/RocketChat/Rocket.Chat&#34;&gt;RocketChat&lt;/a&gt; is worth keeping an eye on, but is not finished or near feature complete as of this writing.&lt;/p&gt;
&lt;h1 id=&#34;on-just-xmpp&#34;&gt;On just xmpp&lt;/h1&gt;
&lt;p&gt;XMPP is a standard for interoperable chat. That&amp;rsquo;s a great goal but also a big challenge. There is no good xmpp desktop client. None support images (Animated gifs I think are a big draw on why people like slack). The default chat client on Ubuntu, Empathy, hasn&amp;rsquo;t seen development in years.
Google used to champion XMPP but hangouts dropped most support except very basic one to one non federated chat. You suck Google.
Thanks to websockets it&amp;rsquo;s really easy to make chat apps. You can learn in a weekend. I made a simple app &lt;a href=&#34;https://github.com/bufke/chat-experiment&#34;&gt;here&lt;/a&gt; with Django and Swampdragon. Learning XMPP on the other hand is hard. There are few resources and existing resources are out of date. XMPP tackles much more like federation so it&amp;rsquo;s unfair to compare it directly with websockets. Still, people are going to use what&amp;rsquo;s easy and that&amp;rsquo;s hosted proprietary services built on websockets. Hopefully open source can catch up. It would be great to see some crowd funded efforts in this space - I&amp;rsquo;d contribute.&lt;/p&gt;</description>
  </item>
  <item>
    <title>The only time I will spam you</title>
    <link>https://david.burke.nyc/blog/the-only-time-i-will-spam-you/</link>
    <guid isPermaLink="true">https://david.burke.nyc/blog/the-only-time-i-will-spam-you/</guid>
    <pubDate>Thu, 02 Oct 2014 12:00:00 +0000</pubDate>
    <description>&lt;p&gt;I&amp;rsquo;m applying to &lt;a href=&#34;https://www.missionmainstreetgrants.com/business/detail/45899&#34;&gt;this $150,000 grant&lt;/a&gt;. We need 250 votes to be considered. Voting for us is an easy way to support projects like &lt;a href=&#34;https://github.com/burke-software/django-report-builder&#34;&gt;django-report-builder&lt;/a&gt; and &lt;a href=&#34;https://github.com/burke-software/django-simple-import&#34;&gt;django-simple-import&lt;/a&gt;.
We spin off these third party apps whenever possible. Burke Software&amp;rsquo;s main focus is an &lt;a href=&#34;schooldriver.org&#34;&gt;open source school information system&lt;/a&gt;. If anything on this blog has helped you please consider giving us a vote. Thank you!
Be warned the website requires a Facebook login. I apologize for that, I don&amp;rsquo;t even have a Facebook account myself and had to use my good old Testy McTest account to vote.﻿&lt;/p&gt;</description>
  </item>
</channel>
</rss>