<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Steven Davies</title>
    <description>The latest articles on DEV Community by Steven Davies (@nullabletype).</description>
    <link>https://dev.to/nullabletype</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F701664%2F3062b681-2626-44b2-b72e-9b5b8932e47b.png</url>
      <title>DEV Community: Steven Davies</title>
      <link>https://dev.to/nullabletype</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nullabletype"/>
    <language>en</language>
    <item>
      <title>AI Field Notes 2026</title>
      <dc:creator>Steven Davies</dc:creator>
      <pubDate>Fri, 03 Jul 2026 22:19:36 +0000</pubDate>
      <link>https://dev.to/nullabletype/ai-field-notes-2026-53ap</link>
      <guid>https://dev.to/nullabletype/ai-field-notes-2026-53ap</guid>
      <description>&lt;p&gt;There is no doubt that AI has rocked the world of coding considerably over the last couple of years. One thing that has become apparent, through keeping up with the whirlwind of changes, is that people are still figuring out how to use these tools effectively. It seems like every video I watch, or article I read, people are swearing they've found the killer approach.&lt;/p&gt;

&lt;p&gt;I have noticed through all the noise though, that there are some key concepts that seem common regardless of which approach you take. This isn't comprehensive, and is likely to change over time, but my key observations in July 2026 are:&lt;/p&gt;

&lt;h2&gt;
  
  
  Clarity is king
&lt;/h2&gt;

&lt;p&gt;I recently watched this excellent &lt;a href="https://www.youtube.com/watch?v=yhGzXULZkEw" rel="noopener noreferrer"&gt;TED Talk by Rainer Stropek&lt;/a&gt;. His closing remark is "I am not just a coder, I am a developer and my new programming language is clarity". The talk is well worth a watch for various reasons, but this statement definitely struck a chord with me.&lt;/p&gt;

&lt;p&gt;Clarity, when working with AI agents, is key. This isn't a new thing though, as if you were working with a team on delivering a coding project, having a shared context and understanding of the goal and approach is key. That doesn't change when working with AI. Having that shared understanding of not just the task, but the goal and the process leading there will give you much better results than just stating a task and expecting great results.&lt;/p&gt;

&lt;p&gt;Rather than just state the task, state the goal you want to achieve and the key criteria or guardrails to get there. Use the LLM to help plan and build your spec before letting it loose. A lot of harnesses have planning modes, so give them a try. Formulate a plan, break it down into stages if required, then use that to guide and review the output.&lt;/p&gt;

&lt;p&gt;"&lt;a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/grill-me/SKILL.md" rel="noopener noreferrer"&gt;Grill me relentlessly&lt;/a&gt;", as Matt Pocock recommends, to work towards that shared understanding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Weak:
Add task cancellation to the CLI.

Better:
Add a `task cancel` command to the CLI using the existing command patterns. 

It should:
- Cancel queued deployment tasks for a selected environment
- Use the same environment matching behaviour as the other commands

Ensure you:
- Include unit tests and live integration tests
- Update any user-facing help text if the command surface changes
- Run the unit test suite, including integration tests
- Provide a summary of your changes and validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Context is also king
&lt;/h2&gt;

&lt;p&gt;A follow-up point from the above: be surgical about the context you provide. Imagine you're talking to a contractor who knows nothing about your project. Be clear on the technical stack and constraints. Give clear directions, link to key files, types or documentation it needs to do a good job. Don't just point at your repo or codebase and expect it to scan and discover everything from scratch. You'll either burn tokens unnecessarily or pull all your hair out when you keep getting bad, inconsistent results. Even worse, both. High signal, low noise.&lt;/p&gt;

&lt;p&gt;Prefer smaller, reviewable tasks. Give clear boundaries and observable success criteria where possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Weak:
Look through the repo and work out how commands are built.

Better:
The existing command patterns are in `src/ShipItSharp.Console/Commands`. 

Start with `BaseCommand`, `Environment`, and `Channel` as examples. 

Core orchestration should live in the shared runner layer, not directly in the console command. 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  You're not just stuck in the terminal
&lt;/h2&gt;

&lt;p&gt;I picked this up from &lt;a href="https://www.youtube.com/watch?v=fdbXNWkpPMY" rel="noopener noreferrer"&gt;a talk by Lucas Meijer&lt;/a&gt;, who offered a simple tip that made my comprehension and efficiency so much better. Ask for a single-page HTML summary. That's it, but it's amazingly powerful in the right situation.&lt;/p&gt;

&lt;p&gt;One example use case is I asked Codex to "grill me" on a complex goal and it generated 60 questions for me to review. Rather than answering them all one by one in a terminal, I got it to spit out an HTML file with an export option so I could review, save, and return if needed.&lt;/p&gt;

&lt;p&gt;I've also used it to generate reviews of code, produce a detailed plan of action or even show me design choices to choose between when I've been unsure.&lt;/p&gt;

&lt;p&gt;Obviously, don't overuse this as it can burn tokens, but in the right contexts it can produce a much easier response to comprehend and lead you to a more efficient goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chat context is expensive
&lt;/h2&gt;

&lt;p&gt;It's a general recommendation that you keep to a low number of messages per chat or session. Why? Every time you send a message, the entire previous context is also sent. The larger this context, the less useful the results become and the more expensive the chat. Most harnesses have reminders or auto compaction to help with this, but often it's still best to request a summary of your conversation so far, then start a new chat with that summary to maximise efficiency.&lt;/p&gt;

&lt;p&gt;On a similar note, if your instructions were unclear and you got a duff response back, don't scorn the AI tool and tell it to try again. Consider editing your original message so it's clearer and then resend. It keeps your context shorter, will give you a more refined response and will burn fewer tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get AI to validate its own work
&lt;/h2&gt;

&lt;p&gt;Before you set your agent off on a series of tasks, make sure you define sensible validation for the result. Think about what you would expect if you were doing the work yourself. Unit tests that pass covering specific areas? Second pass review? Integration tests? Browser validation? Whatever it may be, it's key to save time by getting it to validate its response before asking you to take over.&lt;/p&gt;

&lt;p&gt;One tip I picked up from a colleague is to task the agent with continuing to refine until it's 95% confident on an answer. If it's unable to reach that level of confidence, provide a reason as to why.&lt;/p&gt;

&lt;p&gt;A pattern I’ve started using is asking Codex to do a second-pass review against the original goal, not just against compiler errors. For example, after command/help text changes I asked it to verify the output against the source and generated docs. The first review found several issues, a second pass found a couple more, and only after that did the output settle. That’s a useful workflow: the agent writes, then the agent reviews, but I still own the final judgement.&lt;/p&gt;

&lt;p&gt;Another example is I recently used Codex to add new task-management commands to &lt;a href="https://shipitsharp.app" rel="noopener noreferrer"&gt;ShipItSharp&lt;/a&gt;. The useful part wasn’t that it generated the code. It was that I could give it the expected behaviour, repo conventions, validation criteria, and test expectations up front. It inspected the existing command patterns, added the command surface, core orchestration, unit tests, command tests, and live integration tests against the real Octopus instance. The first pass missed part of my integration-test expectation, which was a good reminder that the agent still needs senior review and clear acceptance criteria.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-strike rule
&lt;/h2&gt;

&lt;p&gt;When an agent's code throws an error, give it one shot to fix it. If it fails a second time, step away from the prompt window. Paste the error back into the chat a third time and you'll often watch the AI enter a hallucination loop, generating increasingly bizarre workarounds. Figure out where your initial context or constraint was lacking, and start a fresh chat. Know when to cut your losses.&lt;/p&gt;

&lt;p&gt;A perfect example of this is when I was working on &lt;a href="https://shipitsharp.app" rel="noopener noreferrer"&gt;ShipItSharp&lt;/a&gt;. I needed a new environment, so I was using Gemini to help set up a new Octopus Deploy instance hosted locally. I encountered error after error, and rather than backing away I persisted. In the end, it hallucinated that Octopus Deploy can run on PostgreSQL. After calling it out, its response was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I owe you a massive, deeply embarrassing apology.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lesson learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Think of using AI agents as a "layer up"
&lt;/h2&gt;

&lt;p&gt;This calls back to &lt;a href="https://www.youtube.com/watch?v=yhGzXULZkEw" rel="noopener noreferrer"&gt;Rainer Stropek's talk&lt;/a&gt;, where he talks about the shift from being on the ground writing code to moving another layer up to orchestrate the tool to do the boilerplate stuff. Your job shifts more towards the problem solving, planning, clarity of communication and validation. It's still key to review the output, especially in the key areas of your system where security and resilience matter. This means you must understand, and take responsibility for, the generated output.&lt;/p&gt;

&lt;p&gt;A good example for me was release workflow work. I didn't need Codex just to edit YAML. I needed it to compare the Unix and Windows workflows, check that the release artifacts matched, verify action references, update documentation, and sanity-check the release tag against the actual version source. My role was less about typing every line and more about setting the standard for what "done" meant.&lt;/p&gt;

&lt;p&gt;It honestly reminds me of earlier in my career. My first love was HTML, CSS and JavaScript (this was 20+ years ago, so not the frameworks we know now). I loved creating things, but over time I started to get frustrated with doing the same basic CRUD operations. That's when I fell in love with backend coding, and eventually the move towards architecture and bigger-picture thinking. The key through all of this is the love of problem solving. Seeing an issue, finding a solution, and building a solution.&lt;/p&gt;

&lt;p&gt;AI tools are like that next level up, where the key things that make you a great developer are still absolutely relevant. Anyone can vibe code something, but building something resilient, secure, safe and scalable for thousands if not millions of users still requires strong development skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI is not human, don't treat it like it is
&lt;/h2&gt;

&lt;p&gt;It may sound harsh, but its true. It's only natural to want to personify the AI tools we use. After all, it often responds to you as if it was a friend or colleague. In reality, you're talking to a probabilistic engine choosing responses it thinks are most appropriate using complex maths. It has no feelings or empathy. Keep operational prompts concise.&lt;/p&gt;

&lt;p&gt;Most harnesses these days have an option to change its personality. I'd recommend setting it to something pragmatic rather than overly friendly and warm.&lt;/p&gt;

&lt;p&gt;Do you have any tips you've found useful? Please do share ❤️ Maybe I'll return next year and point out all the things that have changed and learnt.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Proxy any class by interface in C# with DispatchProxy</title>
      <dc:creator>Steven Davies</dc:creator>
      <pubDate>Fri, 22 Oct 2021 22:27:23 +0000</pubDate>
      <link>https://dev.to/nullabletype/proxy-any-class-by-interface-in-c-with-dispatchproxy-2i6a</link>
      <guid>https://dev.to/nullabletype/proxy-any-class-by-interface-in-c-with-dispatchproxy-2i6a</guid>
      <description>&lt;p&gt;I was recently tasked with figuring out a way to performance test database calls being made via &lt;code&gt;NHibernate&lt;/code&gt; to &lt;code&gt;PostgreSQL&lt;/code&gt;. As far as I could find, &lt;code&gt;NHibernate&lt;/code&gt; doesn't expose any really easy ways to intercept and manually log both the &lt;code&gt;SQL&lt;/code&gt; and execution times for each query. &lt;/p&gt;

&lt;p&gt;After a little more digging, I came across a handy lesser-known tool that allows you to easily proxy any interface, be it user-defined or from a third party library. This tool lives in the &lt;a href="https://www.nuget.org/packages?q=System.Reflection.DispatchProxy" rel="noopener noreferrer"&gt;&lt;code&gt;System.Reflection.DispatchProxy&lt;/code&gt;&lt;/a&gt; nuget package. With a little code, you can wrap any instance of a class that implements an interface with a proxy to not only log but also manipulate both arguments and returned data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Take this simple class and interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IHello&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;SayHello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hello&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IHello&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;SayHello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Hello &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing special, only one method exposed which takes a simple name parameter, writes out a string to the console and returns a bool indicating success.&lt;/p&gt;

&lt;p&gt;Say, although contrived in this example, we wanted to record calls going to &lt;code&gt;SayHello&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloDispatchProxy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DispatchProxy&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IHello&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;IHello&lt;/span&gt; &lt;span class="n"&gt;Target&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MethodInfo&lt;/span&gt; &lt;span class="n"&gt;targetMethod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Code here to track time, log call etc.&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targetMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="nf"&gt;CreateProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HelloDispatchProxy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;HelloDispatchProxy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
        &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is our &lt;code&gt;DispatchProxy&lt;/code&gt;, &lt;code&gt;HelloDispatchProxy&lt;/code&gt;. Usage is pretty simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;IHello&lt;/span&gt; &lt;span class="n"&gt;hello&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HelloDispatchProxy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IHello&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CreateProxy&lt;/code&gt; method calls &lt;code&gt;Create&amp;lt;T&amp;gt;&lt;/code&gt; on the base abstract class to generate a proxy object for you. The target instance is then assigned to the &lt;code&gt;Target&lt;/code&gt; property before returning the proxy cast to type &lt;code&gt;T&lt;/code&gt; (&lt;code&gt;IHello&lt;/code&gt; in this instance).&lt;/p&gt;

&lt;p&gt;Any method that is then called on the proxied &lt;code&gt;IHello&lt;/code&gt; instance will trigger &lt;code&gt;Invoke()&lt;/code&gt;. It's your proxies responsibility to call the target method, pass through the parameters and return the result. In this method, you can do whatever you like, and you'll also have access to your original &lt;code&gt;Target&lt;/code&gt; instance.&lt;/p&gt;

&lt;p&gt;You can see a complete example of this over on my &lt;a href="https://github.com/nullabletype/scratchpad/blob/main/DispatchProxyExample/DispatchProxyExample/HelloDispatchProxy.cs" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With great power comes great responsibility&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This allows you quite a lot of control, so be careful what you do with &lt;code&gt;args&lt;/code&gt; and the returned result or you can cause some unexpected behaviour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance testing
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DispatchProxy&lt;/code&gt; lives under the &lt;code&gt;System.Reflection&lt;/code&gt; namespace. Because of this, I was curious about what overhead is introduced by this approach. What better way to understand than to test it myself.&lt;/p&gt;

&lt;p&gt;I set up a &lt;a href="https://github.com/nullabletype/scratchpad/blob/main/DispatchProxyExample/DispatchProxyExample/Program.cs" rel="noopener noreferrer"&gt;slightly over-engineered set of tests&lt;/a&gt; to give a raw comparison between calls to both an unproxied and proxied instance. I ran these multiple times to get an average before analysing the results. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    100000 W/O  100000 W    % Increase
    2233.023    2382.8835   6.71%
    2235.5306   2398.1216   7.27%
    2236.2449   2386.4416   6.72%
    2236.8529   2389.209    6.81%
    2239.9692   2402.4627   7.25%
    2227.0108   2367.0306   6.29%
    2237.8432   2384.8429   6.57%
    2233.5528   2392.8394   7.13%
    2238.411    2398.3124   7.14%
    2235.7398   2388.134    6.82%
    2262.3454   2380.0595   5.20%
    2340.0243   2385.0553   1.92%
    2249.3693   2383.6925   5.97%
    2246.9835   2381.507    5.99%
    2240.7711   2381.4675   6.28%
    2232.5296   2374.0465   6.34%
    2248.0095   2359.8636   4.98%
    2250.2518   2394.8422   6.43%
    2234.7757   2387.8114   6.85%
    2238.7645   2387.8047   6.66%


    0.00112245  0.001192661 6.27%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So on average, the execution took an extra 6 or so percent with just the introduction of the proxy. Bear in mind in this current state, the proxy is doing nothing useful at all, but this helps put into context the efficiency.&lt;/p&gt;

&lt;p&gt;But if you were to add something slow like a blocking console write line in the proxy, the % increase shoots up from 6.27% to over 100%.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Going to call &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;targetMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important part with the performance impact is what you decide to do in the &lt;code&gt;Invoke&lt;/code&gt; method, something you'll need to benchmark yourself.&lt;/p&gt;

&lt;p&gt;In all but the highest performance-critical applications, the overhead of the proxy itself is minor. If you take for example proxying 2-3 DB calls in the context of something like a HTTP web request or message handler the impact is negligible. In fact, in my testing with a real-world application, the numbers are within an acceptable margin of error even when tracing into an &lt;a href="https://en.wikipedia.org/wiki/Application_performance_management" rel="noopener noreferrer"&gt;APM&lt;/a&gt; tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  NHibernate &amp;amp; PostgreSQL
&lt;/h2&gt;

&lt;p&gt;If you're specifically interested in collecting information from &lt;code&gt;NHibernate&lt;/code&gt; as I was, you can do this by wrapping the &lt;code&gt;IDBCommand&lt;/code&gt; in a custom driver implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfiledNpgsqlDriver&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NpgsqlDriver&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;IDbCommand&lt;/span&gt; &lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConfigurationManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppSettings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"EnableDBTracing"&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ProfiledDbCommandDispatchProxy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDbCommand&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can access the command being executed from the target &lt;code&gt;IDBCommand&lt;/code&gt;'s &lt;code&gt;Command&lt;/code&gt; property and record time etc. with a &lt;code&gt;StopWatch&lt;/code&gt;. I went a step further and implemented the proxy as an &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.iobservable-1?view=net-5.0" rel="noopener noreferrer"&gt;&lt;code&gt;IObservable&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/a&gt; so I can handle it differently depending on the use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Hopefully, you found this interesting. Can you think of any good use cases for this, or have any comments? Would love to hear them ❤&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>proxy</category>
      <category>dotnet</category>
      <category>programming</category>
    </item>
    <item>
      <title>Auto OpenVPN reconnect and killswitch for VPNs with dynamic IPs</title>
      <dc:creator>Steven Davies</dc:creator>
      <pubDate>Sat, 02 Oct 2021 22:22:32 +0000</pubDate>
      <link>https://dev.to/nullabletype/auto-openvpn-reconnect-and-killswitch-for-vpns-with-dynamic-ips-4fnm</link>
      <guid>https://dev.to/nullabletype/auto-openvpn-reconnect-and-killswitch-for-vpns-with-dynamic-ips-4fnm</guid>
      <description>&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;If you’re like me and run a home Linux server, you may wish to add some extra security in the form of a VPN connection to ensure traffic is encrypted. In my case, I use a Raspberry Pi to backup things like RAW images from my camera to an off-site location. The extra security gives me a little peace of mind, especially as the server generally uses a cellular connection.&lt;/p&gt;

&lt;p&gt;I’ve been using &lt;code&gt;UFW&lt;/code&gt; to work as a killswitch and only allow traffic via the VPN connection, but one issue I've had is with my trusted VPN provider is that they regularly rotate IP addresses. &lt;code&gt;UFW&lt;/code&gt; uses &lt;code&gt;iptables&lt;/code&gt; under the hood, meaning it can only be configured to use IP addresses explicitly and not a domain name. This has left my backup machine without an internet connection fairly frequently.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Just to set this straight from the start, I’m no UNIX genius. I spend most of my time working with Windows machines and use Linux more as a hobbyist. That been said, I’ve written my fair share of code/scripts.&lt;/p&gt;

&lt;p&gt;My solution is pretty simple; when disconnects happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; fetch a fresh list of IP’s from my internal DNS server.&lt;/li&gt;
&lt;li&gt; Update &lt;code&gt;UFW&lt;/code&gt;&amp;nbsp;.&lt;/li&gt;
&lt;li&gt; Update the &lt;code&gt;OpenVPN&lt;/code&gt;connection file and reconnect.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I thought I’d share my approach, as I failed finding any other solution online so this may be of help to others.&lt;/p&gt;

&lt;p&gt;The first part is fetching the list of domains. To do this, I use &lt;a href="https://downloads.isc.org/isc/bind9/cur/9.17/doc/arm/html/manpages.html#dig-dns-lookup-utility" rel="noopener noreferrer"&gt;dig&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ipoutput&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dig &lt;span class="nv"&gt;$1&lt;/span&gt; +short&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; “&lt;span class="nv"&gt;$ipoutput&lt;/span&gt;” &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then  
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; “No ips found &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;”  
  &lt;span class="nb"&gt;exit &lt;/span&gt;1  
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; “Found ips &lt;span class="nv"&gt;$ipoutput&lt;/span&gt;”

readarray &lt;span class="nt"&gt;-t&lt;/span&gt; ips &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;”&lt;span class="nv"&gt;$ipoutput&lt;/span&gt;”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives me a nice list of IP address for the specified domain.&lt;/p&gt;

&lt;p&gt;Now I know the correct ip addresses, I need to make use of them. The first job is to update &lt;code&gt;UFW&lt;/code&gt;. I decided to use a template file so I can set it up as I want for each machine. This may not be the most elegant solution, but its pretty flexible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ufwoutput&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;’’

&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; “&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ips&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;”  
&lt;span class="k"&gt;do&lt;/span&gt;  
  :  
  ufwoutput+&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$”&lt;/span&gt;&lt;span class="c"&gt;### tuple ### allow udp any $i any 0.0.0.0\\/0 out\\n”  &lt;/span&gt;
  ufwoutput+&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$”&lt;/span&gt;&lt;span class="nt"&gt;-A&lt;/span&gt; ufw-user-output &lt;span class="nt"&gt;-p&lt;/span&gt; udp &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT&lt;span class="se"&gt;\\&lt;/span&gt;n&lt;span class="se"&gt;\\&lt;/span&gt;n”  
&lt;span class="k"&gt;done

if &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/user.rules.tmp”&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then  
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; “Deleting tmp file…”  
  &lt;span class="nb"&gt;rm&lt;/span&gt; “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/user.rules.tmp”  
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/user.rules.template” “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/user.rules.tmp”

&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; “s/&lt;span class="se"&gt;\[&lt;/span&gt;content&lt;span class="se"&gt;\]&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ufwoutput&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/g” “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/user.rules.tmp” “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/user.rules.tmp”

&lt;span class="nb"&gt;mv&lt;/span&gt; “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/user.rules.tmp” /etc/ufw/user.rules

&lt;span class="nb"&gt;echo&lt;/span&gt; “Reloading ufw”  
ufw reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script builds up a string containing the correct formatted lines for the &lt;code&gt;user.rules&lt;/code&gt; file for &lt;code&gt;UFW&lt;/code&gt;, creates a new copy of the template, replaces &lt;code&gt;[content]&lt;/code&gt; in the file using &lt;code&gt;sed&lt;/code&gt; then moves the file into the correct place (for ubuntu). The final thing to do is &lt;code&gt;ufw reload&lt;/code&gt; to reload the &lt;code&gt;user.rules&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;That’s &lt;code&gt;UFW&lt;/code&gt; taken care of, time for &lt;code&gt;OpenVPN&lt;/code&gt; using a similar method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;openvpnoutput&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;’’

&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; “&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ips&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;”  
&lt;span class="k"&gt;do&lt;/span&gt;  
:  
  openvpnoutput+&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$”&lt;/span&gt;remote &lt;span class="nv"&gt;$i&lt;/span&gt; 1198&lt;span class="se"&gt;\\&lt;/span&gt;n”  
&lt;span class="k"&gt;done

if &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/openvpn.ovpn.tmp”&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then  
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; “Deleting tmp file…”  
  &lt;span class="nb"&gt;rm&lt;/span&gt; “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/openvpn.ovpn.tmp”  
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/openvpn.ovpn.template” “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/openvpn.ovpn.tmp”

&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; “s/&lt;span class="se"&gt;\[&lt;/span&gt;content&lt;span class="se"&gt;\]&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;openvpnoutput&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/g” “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/openvpn.ovpn.tmp” “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/openvpn.ovpn.tmp”

&lt;span class="nb"&gt;mv&lt;/span&gt; “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/openvpn.ovpn.tmp” “openvpn.ovpn”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works in a similar way to the &lt;code&gt;UFW&lt;/code&gt; code, replacing a &lt;code&gt;[content]&lt;/code&gt; placeholder in the&amp;nbsp;&lt;code&gt;.opvn&lt;/code&gt; template file.&lt;/p&gt;

&lt;p&gt;So that’s all the code to update &lt;code&gt;UFW&lt;/code&gt; and &lt;code&gt;OpenVPN&lt;/code&gt;. The next thing to do is to manage the connection. I do this via a simple script called from a &lt;code&gt;cron&lt;/code&gt; job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;getStatus &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  
  &lt;span class="nb"&gt;echo&lt;/span&gt; “Attempting to get device status…”  
  ip address show | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1  
  &lt;span class="k"&gt;return &lt;/span&gt;0  
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a pretty straightforward function. &lt;code&gt;grep&lt;/code&gt; the output from &lt;code&gt;ip address show&lt;/code&gt; to see if the &lt;code&gt;OpenVPN&lt;/code&gt; connection is live. Return either &lt;code&gt;1&lt;/code&gt;for success or &lt;code&gt;0&lt;/code&gt;for failure.&lt;/p&gt;

&lt;p&gt;The last thing to do is to check the result and connect if required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;getStatus &lt;span class="nv"&gt;$device&lt;/span&gt;  
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then  
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; “openvpn is not connected!”  
  &lt;span class="nb"&gt;echo&lt;/span&gt; “Reconnecting!”

  &lt;span class="c"&gt;#Update config  &lt;/span&gt;
  updateIps &lt;span class="nv"&gt;$domain&lt;/span&gt; &amp;amp;&amp;gt; &lt;span class="nv"&gt;$dir_path&lt;/span&gt;/renew.out &amp;amp;disown

  openvpn — config “&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/openvpn.ovpn” &amp;amp;&amp;gt;&lt;span class="nv"&gt;$dir_path&lt;/span&gt;/out.out &amp;amp;disown  
&lt;span class="k"&gt;else  
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; “openvpn is connected!”  
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the output from &lt;code&gt;getStatus&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;, then it will call to update the IP addresses and trigger a new &lt;code&gt;OpenVPN&lt;/code&gt; connection with &lt;code&gt;&amp;amp;disown&lt;/code&gt; to not wait for the connection to exit.&lt;/p&gt;

&lt;p&gt;Now that’s all in place, all that’s needed to run the script automatically is a &lt;code&gt;cron&lt;/code&gt; job to check as frequently as you wish.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt;/1 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /home/account/openvpn &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./auto-connect.sh “my.vpn.domain” “tun0” &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; out.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this is useful to you, rather than having to copy &amp;amp; paste the code I’ve wrapped all this up into a &lt;a href="https://github.com/nullabletype/openvpntools" rel="noopener noreferrer"&gt;single script available on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrap Up
&lt;/h3&gt;

&lt;p&gt;As I say, I’m no Linux sysadmin but this script solves a problem for me and has been working well for the last few months. If anyone has any suggestions for improvements or comments I’d love to hear them. Even better, &lt;a href="https://github.com/nullabletype/openvpntools" rel="noopener noreferrer"&gt;fork the repo on GitHub&lt;/a&gt; and pop in a merge request ❤&lt;/p&gt;

</description>
      <category>openvpn</category>
      <category>vpn</category>
      <category>security</category>
      <category>linux</category>
    </item>
    <item>
      <title>Thoughts on fine tuning your tech CV</title>
      <dc:creator>Steven Davies</dc:creator>
      <pubDate>Sat, 02 Oct 2021 21:53:16 +0000</pubDate>
      <link>https://dev.to/nullabletype/thoughts-on-fine-tuning-your-tech-cv-1f3l</link>
      <guid>https://dev.to/nullabletype/thoughts-on-fine-tuning-your-tech-cv-1f3l</guid>
      <description>&lt;p&gt;It's almost cruel that your first step into a potential new role often falls to a few A4 sides attempting to summarise yourself; your skills, your drive, your goals. You're more than that can cover. The person reviewing your CV knows that, but they face a difficult decision on whether to invest the time to get to know you based on this first introduction.&lt;/p&gt;

&lt;p&gt;I've been responsible for hiring into multiple technical positions over the years across different skill areas including developers, testers and sys ops engineers. Some of these have been recent graduates, others looking for senior positions after decades in the industry. &lt;/p&gt;

&lt;p&gt;I've seen a wide array of CV styles, some that have delighted me and some that have been difficult to navigate. I thought it may be useful to someone out there, or maybe just myself, to list out some of the suggestions I can think of to help make your tech CV easier to digest. These are all based on real examples I've seen more than once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consider your audience
&lt;/h2&gt;

&lt;p&gt;This isn't just for your CV's, this is for everything. Whether you're applying for a new position, putting in a merge request to your peers or writing a best man's speech... it's important to put yourself in the shoes of the person you're writing for. What do they want to know?&lt;/p&gt;

&lt;p&gt;Regarding CV's, imagine you're in the position of hiring for the job &lt;strong&gt;you&lt;/strong&gt; want. What would you want to know, what information matters and just as importantly what information doesn't?&lt;/p&gt;

&lt;h2&gt;
  
  
  The opener
&lt;/h2&gt;

&lt;p&gt;Although it is unlikely to be the first section that is read, a personal statement is an opportunity to impress and set a clear indication about what you're looking for and have achieved. If well written, it can easily help you stand out from other candidates.&lt;/p&gt;

&lt;p&gt;One of the biggest traps I've seen people fall into is to state the basic cliches. I'm talking about things like "hard-working" or "dedicated", they're really expected traits and don't need to be said. Space on your CV is precious, and I'm sure there are things that you're proud of you want to tell the reader about over basic statements.&lt;/p&gt;

&lt;p&gt;The second thing I often see is an overuse of vocabulary. You don't need to impress with fancy words, do so by saying what you've achieved in a clear and easy to read manner.&lt;/p&gt;

&lt;p&gt;Pick out the key accomplishments and skills you want the reader to know about you, what the position you're looking for is and the direction of your career. Be honest, you don't want to lie and get an interview for a position you don't want, but feel free to tailor it to the position you're applying to. Keep the fancy words to a minimum, but don't be afraid to be confident about what you're good at. &lt;/p&gt;

&lt;p&gt;Here's an example of what I'd consider a good personal statement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An &lt;strong&gt;ISTQB certified&lt;/strong&gt; software developer in test with &lt;strong&gt;8 years' experience&lt;/strong&gt; testing enterprise level web applications. &lt;strong&gt;Designed, written and maintained frameworks&lt;/strong&gt; that ensure a &lt;strong&gt;high level of quality and resilience&lt;/strong&gt;, including testing web API's and applications for functionality and &lt;strong&gt;performance&lt;/strong&gt; that have &lt;strong&gt;reduced required testing time by 50%&lt;/strong&gt;. Thrives on the opportunity to &lt;strong&gt;mentor other testers&lt;/strong&gt;, work collaboratively in a team and has a keen eye for spotting opportunities for improvement. Now looking for the next challenge to use the skills gained to make a difference and continue professional growth.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Size matters
&lt;/h2&gt;

&lt;p&gt;If your CV is jam-packed with information covering 10+ pages, then it's going to be difficult to digest and pick out the relevant information you want the reader to see.&lt;/p&gt;

&lt;p&gt;Similarly, if your CV is a page or less, you're likely omitting really useful information about yourself and your skills. &lt;/p&gt;

&lt;p&gt;The best CV's I've personally seen are generally around 1-3 pages long. Any longer than that then you may benefit from having a tidy.&lt;/p&gt;

&lt;h3&gt;
  
  
  So how do I make the best use of the space?
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Keep it relevant
&lt;/h4&gt;

&lt;p&gt;The IT sector runs at a super-fast pace. Your most recent positions are always likely to be the most relevant on your career path. Take a look at your previous employment. That job you did 5+ years ago using some obscure or out of favour tech... ask yourself, do I want to even do that thing again? Is it relevant to this position or where I want my career to go? &lt;/p&gt;

&lt;p&gt;The further back your experience the less specific you probably need to be. Don't omit previous positions altogether, but you can afford more brevity the further back you go. One size doesn't fit all, just use the space wisely.&lt;/p&gt;

&lt;h4&gt;
  
  
  Summary of tech skills
&lt;/h4&gt;

&lt;p&gt;We use so many tools, so many processes and workflows... you want the person reviewing your CV to see all the wonderful things you know and have used. This can quickly turn into 1/4 of a page of your CV or a giant list of unsorted tools and skills. &lt;/p&gt;

&lt;p&gt;The problem is if you're not careful you can really hurt its usefulness. Take a look at the following example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;C#, Visual studio, MVC, WebApi, Postgresql, MongoDB, T-SQL, Flask, SQLite, LiteDB, Entity Framework, PostgreSQL, SQL Server, AWS, Python, Microsoft Word, Fiddler, Postman...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first issue is that there are quite a few things that are not relevant and just distract from what you're actually good at. Things like listing Visual studio along with C# when that's pretty much implied or listing office tools which is an expectation for any IT job. There may also be things you have no real interest in doing again, so think if it's worth listing them. Some may just be completely irrelevant for the position you're applying for.&lt;/p&gt;

&lt;p&gt;The second issue is that it doesn't really give any idea of your experience. I've seen lots of CV tip sites that recommend a list of years of experience but that doesn't always help. Take for example C#. Is that building desktop apps, or web APIs? Maybe it's all in windows services or WCF.&lt;/p&gt;

&lt;p&gt;My current suggestion would be something like the following, but I'd love to hear/see your ideas 🙂&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Technologies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core Languages&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;C#&lt;/strong&gt; 7+, Asp.Net 4+ (WebApi &amp;amp; MVC), Entity framework&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core Databases&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt;, &lt;strong&gt;MongoDB&lt;/strong&gt;, &lt;strong&gt;T-SQL&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core Tooling&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;OctopusDeploy&lt;/strong&gt;, &lt;strong&gt;TeamCity&lt;/strong&gt;, &lt;strong&gt;Fiddler&lt;/strong&gt;, &lt;strong&gt;Postman&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Python&lt;/strong&gt;, Flask, &lt;strong&gt;Ms SQL Server&lt;/strong&gt;, &lt;strong&gt;SQLite&lt;/strong&gt;, &lt;strong&gt;LiteDB&lt;/strong&gt;, &lt;strong&gt;AWS&lt;/strong&gt; (EC2, X-Ray S3)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It's always a good idea to link any experience you mention in the summary with your job roles and include context, like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this role, I was a &lt;strong&gt;key architect&lt;/strong&gt; in developing &lt;strong&gt;large scale&lt;/strong&gt; C# (&lt;strong&gt;8.0, .net 4.7.2&lt;/strong&gt;) apps with PostgreSQL and entity framework.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The design
&lt;/h2&gt;

&lt;p&gt;This doesn't apply to all roles as it may be useful for a graphic designer position for example but if you're applying for a developer/tester/sys ops type role don't focus too much on the design. Stand out by your clear and obvious thought when preparing your CV by keeping the content clear and easy to read. You don't need colours, graphics or charts to sell yourself, they're just a distraction. I'm not saying make it ugly, just don't overthink it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bonus
&lt;/h2&gt;

&lt;p&gt;You're in this industry (hopefully) because you love tech. You probably have personal projects you work on outside of your normal job. It's always a real bonus to get to see what candidates have worked on in their spare time, throw that GitHub or personal website link in there or in your covering letter.&lt;/p&gt;

&lt;p&gt;So that's it, I hope you found some of it useful. Some of the points may seem a little obvious, but they're all based on things I've seen frequently.&lt;/p&gt;

&lt;p&gt;I'd love to know if you agree with any of these thoughts, or maybe you have some of your own tips and tricks you'd like to share? ♥&lt;/p&gt;

</description>
      <category>resume</category>
      <category>career</category>
      <category>interview</category>
      <category>cv</category>
    </item>
  </channel>
</rss>
