Subtopic: Software development / 2

Company and industry news, featured projects, open source code, tech tips, and more.

Supercharge offshore development

Michael Argentini Avatar
Michael ArgentiniWednesday, April 30, 2025

When it comes to offshore software development teams, managing quality and risk creates value. But you also need experienced leadership and oversight for long-term success. Simply adding offshore bodies to a project rarely works, and has diminishing returns.

Here are some process tips for mitigating risk and getting the most value from an offshore team.

  • Also leverage an onshore development partner for leadership and critical systems design. They can create and direct strategy, ensure developers follow patterns, address compliance and security concerns, and perform code reviews to ensure quality. They can also write better code faster, which best suits critical systems development. This is what we do at Fynydd and it works.
  • If possible, your offshore team should mirror your operating hours. Otherwise communications, troubleshooting, and overall progress will lag. It can be beneficial to have expanded availability for handling off-hour requests, but that means the offshore team needs decision-making authority. Otherwise someone in the organization will also need to be available during those hours.
  • Try to get dedicated resources for the long term. When there are offshore staff changes, require that they fully train the replacement(s) before additional staff are brought in. It takes time! New developers, even when they are superstars, need to learn a platform's ins-and-outs before they can meaningfully contribute.
  • Be explicit about who is running the project, give them the appropriate decision-making authority, and enforce a workflow that puts them between ideas and action. Ideally this would be a lead developer from your onshore partner.
  • If the offshore team has novice developers or otherwise low-performers, make sure they are in a learning role and not expected to work on key infrastructure.
  • Perform code reviews. Bad or inefficient code should not be tolerated and is a learning experience that can make your offshore team better.
  • Rely on your lead development partner to facilitate communication. If you find it difficult to communicate with your offshore team, your lead development partner has experience in picking up the nuance, including technical jargon that's hard to understand in any language.

Avoiding the big problems

Some of the issues you'll encounter can be avoided by engaging with an onshore development partner. Here are some tips for keeping the app or service quality high and the risk low.

  • A proper architecture and coding patterns are critical to long-term success. Without a good evolving architecture and consistent coding patterns maintenance is difficult, code readability suffers, and security vulnerabilities are harder to avoid.
  • Compliance is a bear, even with experienced developers. This can range from ensuring organization brand standards, to complying with regional legal requirements (like GDPR), and avoiding copyright violations. There needs to be a focus on these concerns which yields appropriate strategies and resolutions on a consistent basis.
  • Bad code quality is a risk. It's not just about performance and user experience. Bad code could leak information or have vulnerabilities. It could allow bad actors to misuse your app or service. Worse yet, it could facilitate the abuse of your customers.
  • A focus on security is not optional. Properly securing an app or service requires a development team that not only has a security focus, but also the experience and awareness required to implement and maintain a solid security posture. The team members have to be vetted resources with no geopolitical encumbrances and a level of trust commensurate with the app or service in question. For example, bank or government clients may require background checks.
  • Maintaining intellectual capital is crucial. You invested time and money into building a knowledge base as well as an app or service. You need to ensure that the knowledge gained building your app or service will not vanish into the ether.

Want to know more?

There's usually more to the story so if you have questions or comments about this post let us know!

Do you need a new software development partner for an upcoming project? We would love to work with you! From websites and mobile apps to cloud services and custom software, we can help!

We recently converted a website into a native mobile app

Michael Argentini Avatar
Michael ArgentiniTuesday, April 22, 2025

Planning out a long-term strategy for your web project can really pay off. We were recently reminded of that when we were asked to create a mobile app (iOS and Android) for a web-based platform we designed and built several years ago. The platform is Coursabi, a learning platform that ensures growth at each milestone for everyone on your team. You can check it out at https://coursabi.com.

Desktop view of the Coursabi dashboard. Desktop view of the Coursabi dashboard.

When we created the technical strategy we knew that a mobile app was a likely roadmap item. So we chose ASP.NET Blazor as the core platform technology. It allowed us to build a web app that felt like a single page app (SPA). And it gave us several hosting models: server, WASM (WebAssembly), and hybrid mobile. The most intriguing aspect of the Blazor Hybrid model is that unlike hybrid apps of the past, there is no web server running on the mobile device. Instead, all the C# code is compiled to native .NET code, and the web view (an embedded web browser) is only used to render the user interface. So the app runs as a native mobile app!

Various mobile (phone) views. Various mobile (phone) views.

We knew that some features of the platform would have to be altered, since the mobile app has no web server. For example, Coursabi supports the SCORM format for external learning content. And due to security restrictions, they needed a host with a trusted root certificate. So moving that out of the platform and handling the routing changes were both necessary, but totally doable.

Another benefit of a mobile app version of the platform is that in many ways it also simplifies the security model, since the app is only running on the local device, whereas a hosted app needs to manage user state, among other concerns.

Tablet view is a hybrid of desktop and mobile. Tablet view is a hybrid of desktop and mobile.

If you have an ASP.NET-based web application, you can still leverage Blazor Hybrid to turn it into a mobile app. It just needs to first be migrated into a Blazor app. I'd also recommend reviewing your web app for opportunities to make it as mobile-friendly as possible. You don't want your mobile app to look or feel like a website. But those changes not only get you a great mobile app, they also improve how your app looks and feels in a mobile web browser. So you get twice the value.

Want to know more?

There's usually more to the story so if you have questions or comments about this post let us know!

Do you need a new software development partner for an upcoming project? We would love to work with you! From websites and mobile apps to cloud services and custom software, we can help!

The Enigma Machine

Michael Argentini Avatar
Michael ArgentiniSunday, February 2, 2025

The Enigma machine is a cipher device developed and used in the early- to mid-20th century to protect commercial, diplomatic, and military communication. It was employed extensively by Nazi Germany during World War II, in all branches of the German military. The Enigma machine was considered so secure that it was used to encipher the most top-secret messages.

An original Engima Machine, circa 1945 An original Engima Machine, circa 1945

This project is a high performance Enigma Machine emulator that allows you to:

  • Explore historical configurations using the classic 26 letter alphabet (no spaces!)
  • Use for modern quantum-resistant cryptography with the full 95-character ASCII character set.

Just like the physical device, machine state is used to both encipher and decipher text with the same Encipher() method (like a text toggle). Machine state had to match on both the encipher and decipher machines. Each operator would add specific rotors in a specific order, set rotor ring positions and starting rotations, as well as set plug wire positions. This emulator provides virtual versions of all key machine components by way of a deterministic random number generator using AES in counter (CTR) mode.

The emulated components include:

  • Plug board
  • Entry wheel
  • Rotors
  • Reflector

Additionally, characters in the source string that do not exist in the cipher character set are kept as-is in the enciphered text. For example, if you encipher a string with line breaks they are maintained in-place in the enciphered text since neither the classic 26 letter character set nor the 95 character ASCII set contain line break characters.

Performance

The emulator is FAST! When using the full 95 character ASCII character set, a large 800KB text string takes about 1 second to encipher. Typical text sizes encipher in a few milliseconds.

Cipher strength

The physical machine modified with a plug board provided 150 trillion possible settings combinations for the 26 letter character set, with a 10^16 key space for a 3 rotor configuration. 4 rotors yielded a key space of 10^19, 5 rotors yielded a key space of 10^23, and so on.

So by simply using the full 95 character ASCII character set the cipher strength will be exponentially better than the original machine, even without additional rotors or other configuration, and should meet modern quantum-resistant cryptography needs.

Example 1: historical preset

It's easy to create a new virtual Enigma Machine and encipher your own text by using one of the provided presets based on one of the provided historical machine configurations:

  • Commercial Enigma (1924)
  • Wehrmacht and Kriegsmarine (1930)
  • Wehrmacht and Kriegsmarine (1938)
  • Swiss K (1939)
  • Kriegsmarine M3 and M4 (1939)
  • German Railway (Rocket; 1941)
  • Kriegsmarine M4 with thin reflectors (1941):

Using one of the presets is easy:

var message = "FYNYDD IS A SOFTWARE DEVELOPMENT AND HOSTING COMPANY";

var machine = new Machine(new MachineConfiguration
{
    MachinePreset = MachinePresets.Commercial_1924,
    PlugBoardWires =
    {
        { 'A', 'T' },
        { 'B', 'V' },
        { 'C', 'M' },
        { 'D', 'O' },
        { 'E', 'Y' },
    }
});

var enciphered = machine.Encipher(message.ToString());

Assert.NotEqual(message.ToString(), enciphered);

machine.Reset();

var deciphered = machine.Encipher(enciphered);

Assert.Equal(message.ToString(), deciphered);

Example 2: practical usage

It's even easier to use the Enigma Machine for modern encryption, since all you need to provide are a cipher key, nonce, and the number of relevant machine components. There's no need to change rotor ring positions and rotations, or set plug board wire pair values, since your cipher key and nonce are unique and drive the creation of all machine components.

Here's an example of using the Enigma Machine without a historical preset:

var message = @"
Fynydd is a software development & hosting company.
Fynydd is a Welsh word that means mountain or hill.
";

/*
    AES key must be 16, 24, or 32 bytes for AES-128, AES-192, or AES-256
    Nonce or initial counter value must be 16 bytes
*/

var machine = new Machine(
    "ThisIsA32ByteLongSecretKey123456",
    "UniqueNonce12345",
    rotorCount: 6,
    plugWires: 47);

var enciphered = machine.Encipher(message.ToString());

Assert.NotEqual(message.ToString(), enciphered);

machine.Reset();

var deciphered = machine.Encipher(enciphered);

Assert.Equal(message.ToString(), deciphered);

You can also create a custom machine by assembling the virtual components, and more. Check out the project on Github.

Want to know more?

There's usually more to the story so if you have questions or comments about this post let us know!

Do you need a new software development partner for an upcoming project? We would love to work with you! From websites and mobile apps to cloud services and custom software, we can help!

.NET 9 released

Michael Argentini Avatar
Michael ArgentiniTuesday, November 19, 2024

.NET 9 was officially released during .NET Conf. This release feels like a LTS release; full of speed improvements and quality of life features and refinements, even if you only use it as a drop-in replacement for .NET 8.

Ollama Farm

Michael Argentini Avatar
Michael ArgentiniTuesday, September 3, 2024

Ollama Farm is a CLI tool that intermediates REST API calls to multiple ollama API services. Simply make calls to the Ollama Farm REST API as if it were an ollama REST API and the rest is handled for you.

Installation

Install dotnet 8 or later from https://dotnet.microsoft.com/en-us/download and then install Ollama Farm with the following command:

dotnet tool install --global fynydd.ollamafarm

You should relaunch Terminal/cmd/PowerShell so that the system path will be reloaded and the ollamafarm command can be found. If you've previously installed the dotnet runtime, this won't be necessary.

You can update to the latest version using the command below.

dotnet tool update --global fynydd.ollamafarm

You can remove the tool from your system using the command below.

dotnet tool uninstall --global fynydd.ollamafarm

Usage

Ollama Farm is a system-level command line interface application (CLI). After installing you can access Ollama Farm at any time.

To get help on the available commands, just run ollamafarm in Terminal, cmd, or PowerShell. This will launch the application in help mode which displays the commands and options.

For example, you can launch Ollama Farm with one or more host addresses to include in the farm:

ollamafarm localhost 192.168.0.5 192.168.0.6

In this example, Ollama Farm will listen on port 4444 for requests to /api/generate. The requests are standard Ollama API REST requests: HTTP POST with a JSON payload. Requests will get sent to the first available host in the farm.

You can also change the default Ollama Farm listening port of 4444:

ollamafarm --port 5555 localhost 192.168.0.5 192.168.0.6

And if you run any ollama hosts on a port other than 11434, just specify the port in the host names using colon syntax:

ollamafarm --port 5555 localhost:12345 192.168.0.5 192.168.0.6

Ollama Farm requests

Requests made to the Ollama Farm service will be routed to one of the available Ollama API hosts in the farm. Requests should be sent to this service (default port 4444) following the standard Ollama JSON request format (HTTP POST to /api/generate/). Streaming is supported.

Hosts are checked periodically and are taken offline when they are unavailable. They are also brought back online when they become available.

To optimize performance Ollama Farm restricts each host to processing one request at a time. When all hosts are busy REST calls return status code 429 (too many requests). This allows requesters to poll until a resource is available.

Additional properties

  • farm_host : Request a specific host (e.g. localhost:11434)
  • farm_host : Identify the host used

Example:

{
    "farm_host": "localhost",
    "model": ...
}
Screenshots

Want to know more?

There's usually more to the story so if you have questions or comments about this post let us know!

Do you need a new software development partner for an upcoming project? We would love to work with you! From websites and mobile apps to cloud services and custom software, we can help!

Animate CSS auto height without JavaScript

Michael Argentini Avatar
Michael ArgentiniSunday, August 4, 2024

Web developers rejoice! There's an easy way to animate the height of an HTML element even if the height is dynamic, determined by its content, with only CSS. This is typically used for navigation menus and the like, and now it's much easier to code and maintain.

The strategy is to actually animate the grid-template-rows not the height. For example, take the following HTML markup:

<div class="menu">
    <div class="inner-wrapper">
        <p>Here is some content.</p>
        <p>Here is some content.</p>
        <p>Here is some content.</p>
    </div>
</div>

The CSS for this markup would be:

.menu {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 100ms;
}

.menu.active {
    grid-template-rows: 1fr;
}

.menu .inner-wrapper {
    overflow: hidden;
}

Initially the outer div will be hidden since it has no overflow and the grid template rows are zero. When you add active to the outer div element's class list, the browser will animate the transition from zero row height to 1fr, which essentially means the height it needs for its content to render.

Want to know more?

There's usually more to the story so if you have questions or comments about this post let us know!

Do you need a new software development partner for an upcoming project? We would love to work with you! From websites and mobile apps to cloud services and custom software, we can help!

Automate the installation of all your Mac apps using Homebrew and Mas

Michael Argentini Avatar
Michael ArgentiniWednesday, July 10, 2024

On macOS it's pretty easy to automate the installation of all your apps, including Mac App Store apps, for those times when you get a new Mac or wipe your current one. As a software developer I find this capability indispensable, as would any professional or power user.

All you need to do is install Homebrew and then use it to install mas (which is an acronym for Mac App Store). Once they are installed, you can install all your software using a convenient Bash script. Homebrew will be used to install non-store apps, and mas will handle installing the Mac App Store apps.

Note: only Mac App Store apps you have already installed previously can be installed with mas. You cannot install new apps you have never installed from the store.

Why do this?

The most obvious reason to script out your software installations is that it greatly reduces the time to set up a new Mac, as well as ensure that you don't forget to install one or more apps. It also provides a way to update all the apps at once via the brew upgrade command. And it also provides a way to update apps that don't have their own update feature.

Apps installed with mas will be updated normally by the Mac App Store.

How does this work?

In order to use this process you need to know the names of the Homebrew formulae/casks for each application, and you also need to know the IDs of the Mac App Store apps for mas. Fortunately this is super easy.

First, Homebrew has a tool for finding software available in their service at https://formulae.brew.sh/. Simply use this to find your apps and make sure you're installing the right ones. Those listed as “casks” are GUI Mac apps (normal apps you don't run from the Terminal). Ones listed as “formulae” are typically command line tools run from the Terminal or services without an interface.

Second, for Mac App Store apps you simply use mas to list what's currently on your computer from the Mac App Store.

mas list

This will give you a list of currently installed apps from the Mac App Store, with their IDs:

1569813296  1Password for Safari      (2.10.0)
975937182   Fantastical               (3.7.12)
409183694   Keynote                   (13.0)
etc.

You can also search for Mac App Store apps by name using the mas search command:

mas search Xcode

This will show a similar result for matches. You can even install all search results with a single “lucky” command. See the mas help for these and other options.

Script example

Here's an example of a Bash script to get you started. I keep a similar script updated as I use new apps or stop using others. Then I'm ready to go when I have to set up a new or wiped Mac.

# Install Homebrew

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# App Store Automation

brew install mas

# Install App Store Apps

mas install 409183694   # Keynote
mas install 409201541   # Pages
mas install 409203825   # Numbers

# Install Non-Store Apps

brew install --cask firefox
brew install --cask knockknock
# etc.

You can name the Bash script something like install-software.sh and execute it in a Terminal like this:

zsh install-software.sh

The first time you use the script will absolutely justify the time spent writing it. The second time you run it you will thank your past self for being so smart 😉.

Want to know more?

There's usually more to the story so if you have questions or comments about this post let us know!

Do you need a new software development partner for an upcoming project? We would love to work with you! From websites and mobile apps to cloud services and custom software, we can help!

SqlPkg for Microsoft SqlPackage

Michael Argentini Avatar
Michael ArgentiniMonday, July 1, 2024

SqlPkg is a 64-bit .NET command line (CLI) wrapper for the Microsoft SqlPackage CLI tool with the goal of making common backup and restore operations easier and more powerful. It does this through new Backup and Restore actions that provide additional features like the exclusion of specific table data in backups and destination prep prior to restore.

Visit the repository to see how you can install this tool to begin using it right away.

New action modes:

/Action:backup
This mode is equivalent to Action:Export to create a .bacpac file, with the following differences.

  • Specify one or more /p:ExcludeTableData= properties to exclude specific table data from the bacpac file. The table name format is the same as the /p:TableData= property.
  • /SourceTrustServerCertificate: defaults to true.
  • /SourceTimeout: defaults to 30.
  • /CommandTimeout: defaults to 120.
  • /p:VerifyExtraction= defaults to false.
  • Destination file paths will be created if they do not exist.

/Action:restore
This mode is equivalent to Action:Import to restore a .bacpac file, with the following differences.

  • The destination database will be purged of all user objects (tables, views, etc.) before the restoration.
  • If the destination database doesn't exist it will be created.
  • /TargetTrustServerCertificate: defaults to true.
  • /TargetTimeout: defaults to 30.
  • /CommandTimeout: defaults to 120.
  • Destination file paths will be created if they do not exist.

/Action:backup-all
This mode will back up all user databases on a server.

  • Provide a source connection to the master database.
  • Provide a target file path ending with 'master.bacpac'. The path will be used as the destination for each database backup file, ignoring 'master.bacpac'.
  • Optionally provide a log file path ending with 'master.log'. The path will be used as the destination for each database backup log file, ignoring 'master.log'.
  • Accepts all arguments that the Backup action mode accepts.

/Action:restore-all
This mode will restore all *.bacpac files in a given path to databases with the same names as the filenames.

  • Provide a source file path to 'master.bacpac' in the location of the bacpac files. The path will be used as the source location for each database backup file to restore, ignoring 'master.bacpac'.
  • Provide a target connection to the master database.
  • Optionally provide a log file path ending with 'master.log'. The path will be used as the destination for each database backup log file, ignoring 'master.log'.
  • Accepts all arguments that the Restore action mode accepts.

When not using SqlPkg special action modes, the entire argument list is simply piped to SqlPackage and will run normally. So you can use sqlpkg everywhere SqlPackage is used.

Installation

1. Install Microsoft .NET

SqlPkg requires that you already have the .NET 8.0 runtime installed, which you can get at https://dotnet.microsoft.com/en-us/download.

Because SqlPkg uses Microsoft SqlPackage, you will also need to install the .NET 6.0 runtime as well as SqlPackage.

dotnet tool install -g microsoft.sqlpackage

2. Install SqlPkg

Run the following command in your command line interface (e.g. cmd, PowerShell, Terminal, bash, etc.):

dotnet tool install --global fynydd.sqlpkg

Later you can update SqlPkg with the following command:

dotnet tool update --global fynydd.sqlpkg

Uninstall

If you need to completely uninstall SqlPkg, use the command below:

dotnet tool uninstall --global fynydd.sqlpkg
Screenshots

Want to know more?

There's usually more to the story so if you have questions or comments about this post let us know!

Do you need a new software development partner for an upcoming project? We would love to work with you! From websites and mobile apps to cloud services and custom software, we can help!

Fdeploy for web app deployments

Michael Argentini Avatar
Michael ArgentiniSaturday, June 1, 2024

The Fdeploy project is a command line interface (CLI) application that can use simple YAML config files in your ASP.NET web projects to define deployment to one or more remote environments over SMB, like over a VPN connection using a network file share.

It can be configured to clean and purge the project, build, and publish, and even add files and folders to the published output prior to deployment.It can then deploy with various rules like "path ignore", "always update path", "clean orphaned files and folders", and more. You can even define content that can be deployed without taking the web app offline. And when it does, it uses an app offline file that you can also customize in the YAML settings.

Fdeploy also has robust retry support. When files are in-use on the remote server it will retry one or more times and wait a specified number of seconds between attempts.

Visit the repository to see how you can install this tool to begin using it right away.

Installation

1. Install Microsoft .NET

Fdeploy requires that you already have the .NET 8.0 runtime installed, which you can get at https://dotnet.microsoft.com/en-us/download.

2. Install Fdeploy

Run the following command in your command line interface (e.g. cmd, PowerShell, Terminal, bash, etc.):

dotnet tool install --global fynydd.fdeploy

Later you can update Fdeploy with the following command:

dotnet tool update --global fynydd.fdeploy

Uninstall

If you need to completely uninstall Fdeploy, use the command below:

dotnet tool uninstall --global fynydd.fdeploy
Screenshots

Want to know more?

There's usually more to the story so if you have questions or comments about this post let us know!

Do you need a new software development partner for an upcoming project? We would love to work with you! From websites and mobile apps to cloud services and custom software, we can help!

Namecheap DNS

Michael Argentini Avatar
Michael ArgentiniWednesday, May 15, 2024

The NameCheap project builds a command line interface (CLI) application that can add and remove text records using the NameCheap API.

It was originally built to allow for creating wildcard TLS certificates using win-acme (Let's Encrypt) on an IIS server. Creating wildcard certificates with win-acme requires DNS host validation. This application can be used with the win-acme script feature to allow it to communicate with the NameCheap API and create/delete TXT records that will validate domain ownership.

This tool does not support the complete NameCheap API. But it does handle the challenging task of adding and removing text records. Why is this challenging? The NameCheap API does not have functions to add or remove individual records, so the entire set of records must be downloaded, modified, and sent back.

How to install

Download the project and publish it from the root project folder as below.

dotnet publish Fynydd.NameCheap/Fynydd.NameCheap.csproj -o publish -p:PublishSingleFile=true -c Release -r win-x64 --self-contained

In the publish folder, edit the appsettings.json file and supply your own values.

{
    "NameCheap": {

        "ApiKey": "{your namecheap API key}",
        "UserName": "{your namecheap username}",
        "ApiUserName": "{your namecheap API username}",
        "ClientIP": "{a whitelisted IPv4 address}"
    }
}

Note:

  • You can enable the NameCheap API and get a key on their website.
  • UserName and ApiUserName are usually the same value, and it is usually the user name you use to sign in to NameCheap.
  • ClientIP is a whitelisted IP address allowed to connect to the API. These whitelisted addresses can be added to NameCheap when/where you enable the API on their website. Note: API calls will check your current WAN IP with the one you provide in the settings. So they need to match.

Once the appsettings.json file is modified, put the contents of the publish folder on your server and you should be able to use the executable with win-acme or any other tool by calling it with a fully qualified path.

Usage

The command line is in the format below:

Fynydd.NameCheap.exe [create|delete] [hostname] [name] [value]

Some examples include:

Fynydd.NameCheap.exe create example.com * testrecord=yaddayadda
Fynydd.NameCheap.exe create example.com my.api mykey=yaddayadda
Fynydd.NameCheap.exe create example.com my.txt "val1=yadda; val2=yadda"

So in win-acme you would set your create script arguments to this:

create {ZoneName} {NodeName} {Token}

Likewise, your delete script arguments would be:

delete {ZoneName} {NodeName} {Token}

macOS and Linux

The tool can be used on Linux or macOS as well. If the published executable doesn't run on macOS you may need to manually sign the published application using something like this:

cd publish
codesign -s - Fynydd.NameCheap

Want to know more?

There's usually more to the story so if you have questions or comments about this post let us know!

Do you need a new software development partner for an upcoming project? We would love to work with you! From websites and mobile apps to cloud services and custom software, we can help!

© 2026, Fynydd LLC / King of Prussia, Pennsylvania; United States / +1 855-439-6933

By using this website you accept our privacy policy. Choose the browser data you consent to allow:

Only Required
Accept and Close