Saving important files with an encrypted virtual drive and BitLocker

Since Windows 8.1, Windows provides the option to create a virtual hard drives (VHDs) from the Disk Manager. This basically means you’re able to create a file that can be mounted on your computer just like any old drive. Its intended purpose is to be used in virtual applications (such as Hyper-V), but since you can mount those drives on your Windows PC like normal, you can easily use them as a “flash drive” of-sorts that is always on your computer.

I leveraged that ability to store many of my files in VHDs. Right now, most of my development work is in a VHD, since I can easily back up by just copying a single file across multiple hard drives (which means I can simply do away with using Macrium Reflect or dd or some other copying tool). It’s been a lifesaver for many things: namely, I was able to immediately start working after my laptop broke down because I had a VHD copy, and I no longer had to extract those files from a ZIP file, download them from online, nor did I need to use an entire drive for saving things.

Around 2018, I remembered BitLocker exists for Windows 10 Pro. To summarize BitLocker, it uses the computer’s Trusted Platform Module (TPM) or a startup key saved on a flash drive (if your computer doesn’t have a TPM) to encrypt entire drives. To open a BitLocker-encrypted drive, you’ll need to enter a password.

Given that you can create virtual drives, and that you can encrypt drives, I’ve been storing some rather important secrets in a BitLocker-encrypted VHD for a while now. Such a system doesn’t need anything more than just Windows, and you have the option of creating such a setup as soon as Windows is installed.

Quick disclaimer: This only works for Windows 10 Pro and above. Windows 10/11 Home does not provide BitLocker. Also, I made this tutorial for a friend, and is primarily intended for those who are not tech-savvy but still want to keep their files secure with whatever they have now.

So, how do we get started?

1. Create a virtual hard disk

First, open Disk Management. There’s many ways to do this, and it’s up to you on what you want to do.

  • Press your Windows Key + R to open the Run dialog, enter “diskmgmt.msc” and press “OK”.
  • Search for “Disk Management” on Windows search.
  • Open your start menu, look for “Disk Management” (commonly in the “Windows Administration Tools” folder), and open it.
  • Open your Control Panel, find “Windows Tools” when viewing by large icons, open “Computer Management”, and select the “Disk Management” option on the left side (under “Storage”).

Whichever method you follow, you should now see the Disk Management panel.

The Disk Management panel on Windows 11.

On the toolbar, click on “Action”, and “Create VHD”. What should appear is a dialog that looks like this.

The VHD creation menu.

Specify a location by clicking the “Browse” button, or type the location in yourself. For the size: this is up to you. For my encrypted drive, I went went a decent 16 GB, which is enough to fit a large amount of documents, images, and videos. You can even go as low as 512 MB if you’re not storing much anyway.

For the format, select VHDX unless you’re not insane. This should automatically select the virtual hard disk type: dynamically expanding. If you want the drive to take up all the space it will ever need, set it to fixed size (note that you will have a file almost as big as the size you entered earlier, as expected).

Once you’ve pressed “OK”, your disk should appear at the bottom of the lower panel. It will have a red arrow pointing down, and text saying “Not Initialized”. To be able to use this drive, you’ll need to initialize it first, by right-clicking the drive and clicking on “Initialize Disk”.

That Initialize Disk button is what you’re looking for.

You’ll be asked which partition style you want to use, either Master Boot Record (MBR) or GUID Partition Table (GPT). Since BitLocker only works for Windows 10 and above anyway, you can get away with just using GPT here.

The Initialize Disk dialog.

After this, you can now create the partition responsible for holding all your files. This is the last step in creating the VHD. Right click on the “Unallocated” block, and select “New Simple Volume…”

This time, that New Simple Volume… button is what you’re looking for.

You’ll see a wizard pop up, which will guide you through creating a partition.

You can just rapidly click on your “Next” button here, since none of the options are that important. However, you can chose to configure the drive letter (or path) that the partition will mount to, and the name of the partition to create. Stick to NTFS, here, since only Windows 10 and above supports BitLocker anyway. No need to experience the pain of using a FAT32 partition for no reason. Once you’re done with that, click on the “Finish” button and you should find your virtual drive on your list of drives in Windows Explorer.


Congratulations! You now have a virtual drive. Now let’s encrypt it with BitLocker.

2. Encrypt the drive with BitLocker

Head into your Control Panel, set the view to “Large Icons”, and look for “BitLocker Drive Encryption”.

It’s at the top. Tiny button, I know.

From there, you should be able to see the drive you just made, with a “Turn on BitLocker” link when opening the dropdown.

There’s the link you have to click on. It should be obvious.

Select on “use a password to unlock the drive” and type in the password you want to use. You can also use a smart card (if you have one).

I went with Hank Anderson’s password for this one.

You’ll be asked to save your recovery key somewhere. This is used in case you ever forget your password or if you lose your smart card.

Back it up with all four options if you’re fancy.

You can also choose to save it to your Microsoft account, in case you’ve logged into Windows with one. Note that this step is required and will prevent you from proceeding if you didn’t save it anywhere.

You then have the option of selecting how much of the drive you want encrypted. Since we just made the drive a few minutes ago, you can just encrypt the entire drive without fuss.

Select the second option (entire drive), in case that wasn’t obvious yet.

You then have another question on encryption mode if you’re on Windows 10 version 1511 and above. Since this drive is staying on your PC, just stick with the new encryption mode.

Do I really have to write a caption for all of these? Bruh.

Once you press next, you’ll be asked if you’re ready to start encrypting. Click on “Start encrypting” whenever you’re ready. This usually won’t even take a few seconds since the drive is practically empty. After that, congrats! Your drive is now encrypted with BitLocker. You can prove this by opening the “BitLocker Drive Encryption” page on the Control Panel earlier, and see settings related to the drive’s BitLocker (including an option to turn it off).

Additional options presented when BitLocker is enabled.

This drive will automatically lock itself whenever it is unmounted. Unmounting the drive is as easy as right clicking it in the Windows Explorer and clicking “Eject”.

Final notes

If you want to remount the drive, just double click on the VHDX file you created earlier.

Whenever you’re remounting the drive after ejecting it, you might get an “Access is denied” message. This is expected, since you’ll need to enter your password first before accessing the drive. To unlock it, click on the notification from BitLocker Drive Encryption that appears when the drive is remounted.

This thing. Click on this.
Then type your password into this.

After that, your encrypted drive should just open normally. Don’t forget to eject it when you’re done using it! Or else you would defeat the purpose of having an encrypted drive.

If you want to back up the drive, just copy the VHDX file somewhere else. It will retain its encryption no matter how many times you move/copy the file, as long as you don’t damage it.

How secure is BitLocker and should you trust it for storing important data? Look no further than the programmer’s best friend, Stack Exchange, for an answer.

In general, Bitlocker is secure and is used by companies all over the world. You can’t just extract keys out of the TPM hardware. Evil maid attacks are mitigated also since TPM will validate the pre-boot components to make sure that nothing has been tampered with. Booting into another OS like Linux to extract passwords or the data will not be possible also, since the TPM will not release its keys if it sees you’re booting into another OS (even if it is another Windows OS).

from “Is Windows BitLocker secure?“, CC BY-SA 3.0

Then again, I could be entirely missing the mark and the use of BitLocker. But who cares? At least I don’t have to pay for some freemium folder locking program.


Easy npm version synchronization for monorepos

TL;DR: Head to the Bespoke…er? section.

I sometimes have to unexpectedly operate a monorepo because of how some of my projects work (i.e., has a backend and a frontend). This has happened to my PAGASA Parser Web project and much recently, my Zoomiebot Wikipedia bot. Everything works fine and dandy at first, until eventually I get hit by a truck: how am I supposed to synchronize version numbers between both frontend and backend?

Let’s look at the structure first: the repository has a backend and frontend folder, each with their own package.json files and their own set of dependencies.


The “broke” way of doing this would be to manually run npm version on every workspace. This, unfortunately, does not scale up. You shouldn’t spend more than a minute updating version numbers, and definitely not for a monorepo with +20 packages. Not to mention the amount of Git commits you’d be making without setting --no-git-tag-version on.


So what’s a better method? You can, of course, rely on a script to do this for you. This works decently as well — you can use a JavaScript file (for cross-platform compatibility) to just rewrite all the package.json files, add them into the Git commit with an npm hook, and then call it a day. Sure, this works. I even did it once. Is it the best solution? Hell no. You need a few extra scripts and some hacky workarounds with child_process. Works? Yes. Good? No.

So let’s try to find a better method. Introducing: npm workspaces. You’ve probably heard of these before, and you might even be using it for your own monorepo-related business. In short, you can set up a workspace with a package.json at root of the repository, and then declare those smaller packages as workspaces of the root package.

// /package.json
    // ...
    "workspaces": [
        // Each of the folders in this array have their own
        // package.json files.
        "package-a", // /package-a/package.json
        "package-b"  // /package-b/package.json
    // ...

So now, we can just easily run npm version on the root repository and it’ll update everything right? WRONG! It’ll only update the root repository’s version. Frustrating, I know. But it’s just because you’re running it wrong right? Right?

The npm version documentation indicates that you need to use the --workspaces flag to update the version on all workspaces. Easy enough, right? WRONG AGAIN! This will update the version on all workspaces, but never actually update the version for the root package. Frustrating again, I know. But we’re almost there!


If you wanted to run some task after the npm version command on the root repository, you’d usually use a hook. In this case, we want a hook to update the versions of all workspaces. Conveniently, npm provides a hook right before making the Git commit but after the root repository’s version has been update: version. Also conveniently, npm exposes the current package.json being run as environment variables! In this case, we’re specifically looking for $npm_package_version (all lowercase, yeah, I know, right?) which also conveniently updates even in the middle of the version command! How… convenient!

npm workspaces also consolidates your node_modules folder so that you don’t end up reinstalling dependencies across packages. There’s many other advantages workspaces have, but I digress for now.

So, with that in mind, our version script will now look like this in the root repository’s package.json, right?

// /package.json
    // ...
    "workspaces": [
    "scripts": {
        "version": "npm version $npm_package_version --no-git-tag-version --workspaces && git add ."
    // ...

Yes. Not kidding this time; this will actually work. Will it work for everyone though? Still no. There’s one glaring problem here that you might have already noticed if you develop on Windows: the shell $ symbol is used to reference the version environment variable. That’s a big no-no for fancy ol’ Windows which needs %s. So how do we address this? Unfortunately, you will need a dev dependency for this, but one that you probably already had if you had any sort of cross-platform npm script: cross-env.

As it turns out, cross-env also provides a very useful script aside from cross-env, and that’s cross-env-shell. With cross-env-shell, you can write environment variables in their shell format and it’ll just work on Windows. You can install cross-env as a development dependency with the following command:

npm install --save-dev cross-env


So what’s the real solution? If you came here without reading the other sections, install cross-env first. With that out of the way, here it is:

// /package.json
    // ...
    "workspaces": [
    "scripts": {
        "version": "cross-env-shell \"npm version $npm_package_version --no-git-tag-version --workspaces\" && git add **/package*.json"
    // ...

An explanation to what the script does is below:

  • Latching on the version hook has the script run after the version is updated on the package.json file but before the Git commit and tag is made.
  • cross-env-shell runs the following command, replacing any $ environment variables if needed (because Windows support is a thing).
  • npm version $npm_package_version will set the npm versions with the following conditions/arguments:
    • --no-git-tag-version ensures that an extra Git commit won’t be made for every version update.
    • --workspaces tells npm to run this command on all workspaces.
  • git add **/package*.json will add in the modified package.json files to the staged Git changes. You can get away with using . if you feel fancy, since npm checks if the Git repo is clean before making any version changes, but this just seems safer for me.

After the hook runs, all versions across all packages and the root package will be synchronized. How exciting! Running npm version patch on the root repository will now properly bump the version number and update the version for all dependencies!

C:\Users\chlod\Workspace\haha_you_looked>npm version major

> version
> cross-env-shell "npm version $npm_package_version --no-git-tag-version --workspaces" && git add **/package*.json


C:\Users\chlod\Workspace\haha_you_looked>git status
On branch master
nothing to commit, working tree clean

This doesn’t prevent anyone from running npm version on a subpackage and updating the version of that specific package. If you want to ban that entirely, just attach a command that always exits with a non-zero code to the preversion hook. You should already know how to do this, so I’ll leave that up to you!

That’s all for now, folks! Enjoy your version-synchronized monorepo packages.


An extremely comprehensive guide to running your own school elections with old software

Nothing drives me more crazy than abandonware.

A modified version of Halalan tweaked for the 2021 PSHS-CVC SSG Elections. It includes a few graphical and functional changes, the most prominent in this screenshot being the notice right before the form box.

Facilitating school elections is no easy feat. Not because it’s hard for students to understand how to vote, but because the software is hilariously painful to deal with. The software currently suggested by PSHS-CVC teachers, and also used by the University of the Philippines, Halalan, is 9 years old as of writing. It relies on older software, and makes life extremely difficult. But no worries, since this “guide” of sorts will help you through everything you need to know.

Do note, however, that you will be severely violating some common security practices for the sake of compatibility. It is suggested that you move on to a different platform for elections, or else you might compromise the elections’ security.

A screenshot of the Halalan GitHub repository, dating its last commit back to September 19, 2012.

Halalan, literally “election” in Filipino, is an open-source voting platform developed by University of the Philippines students in early 2006. It supports both English and Filipino (albeit the language configured on the server side) and the first-past-the-post voting.

Getting started

Prior to literally anything at all, you should probably scout for where you’re going to host the election server. As a small benchmark, we ended up using a single-vCore, 2 GB RAM VPS for the elections. OVH gave this exact configuration and was also available on their Singapore datacenter, which made latencies to the Philippines extremely low. Luckily, this dedicated server allowed us to perform the elections smoothly with peak RAM usage hitting only up to 40%. Granted, we did spread out the election period quite a bit in order to decrease simultaneous traffic, but the server would have been perfectly capable of handling a hundred voters at the same time.

That said, if you’re planning to run this on an existing server that may have its own web server already installed, you might want to use Docker to completely isolate the software versions that you need. Spoiler alert: unless you’re using something as ancient as Ubuntu 12.04, it likely won’t run. Halalan requires PHP 5, which was declared EOL at the start of 2019. Luckily there’s not much restrictions on what MySQL server to use, but we’ll get to that later.

For now, you can get away with installing Apache HTTP Server (httpd) 2.4 together with PHP 5.6. This highly depends on what flavor of Linux you have installed, so I’ll leave this up to you. If you happen to be using Docker, your life will be made easy if you just use the provided PHP 5.6 images. But then there’s a catch! You need gd (with at least PNG and JPEG support so that you won’t lose your mind) and the rewrite module for httpd. So you’ll need to get those installed and enabled as well.

After installing httpd and PHP 5.6, you’ll need to get MySQL up and running. I’d actually advise against using MySQL, and instead use a fork of it, MariaDB, as it contains significant performance improvements which can help make the elections much more smooth. The latest version of MariaDB (currently 10.6.4) works perfect for the job, as it is backwards compatible with the required MySQL version for Halalan.

Now, however, is where I introduce the first dealbreaker for Halalan: you must disable strict mode in your SQL server’s configuration. Merely attempting to open the website without disabling strict mode will show an error immediately on load. That’s definitely something you should not be looking for.

After disabling strict mode, you can now begin installation of Halalan. You can download the latest version as a zip file from GitHub, or just clone the repository on your server. You’ll need to extract the repository onto your document root (probably /var/www/html) to get the files in and then modify the system/application/config/database.php file to set up the database. After entering your configuration, the installation menu should now be accessible. This is located in the /install subdirectory of your web server. Halalan will present you with a few options for setup.

The configuration screen for Halalan.

The configuration here is mostly up to you. For the SSG elections, we went with PINs disabled, candidate details showing, and password lengths at 10 characters. This would make the election process simple and straightforward for students, without compromising security.

Here comes the second dealbreaker for Halalan: passwords are stored as SHA-1 hashes. I don’t have to go in-depth on how insecure SHA-1 is since there’s hundreds of articles about that already. When you’re dealing with elections, you really want to aim for the best security possible. SHA-1 just doesn’t make that any helpful. You can spend some time in the depths of the code trying to replace all the instances of SHA-1 hashing with SHA-256, or better yet Blowfish with password_hash, but this takes time, effort, and technical knowledge of what you’re doing and what you might get wrong. Granted, all passwords are automatically generated by Halalan, so this might not be an issue for you as long as you tightly secure your server.

Once you’ve configured your server and moved the configuration file to its final destination, you will now be able to configure your elections. Congratulations! You can already stop here if you really just wanted to play around. If you want to use this in production though, you need to secure your server quite a bit before you get the elections going.

Locking down

You might be tempted to just start the elections with this setup. If you do that, you’ll be leaving some vital components exposed to the wild by default (at least on Debian or Ubuntu). Your next step would be to open up a firewall to allow access only to ports 80 and 443 (if you’re using SSL, which you should). You might wonder just how the hell are you supposed to control the server if you block off all ports. I present to you the wonders of ZeroTier: a service that’s kind of like Hamachi, but completely free and open source.

ZeroTier opens up a virtual private network between you and the server, and it counts as a completely separate interface, which means your firewall can easily whitelist that interface. You can do things like access the SSH and SQL ports over the private connection without risking exposure to the rest of the internet. There’s separate tutorials for this, so I’ll spare you the details on how exactly this is done.

After getting ZeroTier set up, whitelisting ZeroTier connections in nftables is as easy as two lines:

udp dport 9993 accept  # ZT communication port
iifname zt* accept     # or jump to a different chain

When you’re done with that, you’ve mostly secured yourself against intrusion. It is still best to follow other security practices though, such as only allowing public key authentication with SSH and disallowing root logins for both SSH and SQL.

Next step is to get a domain. A cheap one if possible. The reason why you’d likely want to register a domain is because Let’s Encrypt provides SSL certificates completely for free, and because requesting an SSL certificate IP address is extremely expensive. If you had the money to buy an SSL certificate for your election server’s IP address, you might as well have bought a domain name, heck, even invested in a custom-made election server.

Your focus now is to improve on client-side security, which means adding in an SSL certificate so that the elections can be conducted through HTTPS. Of course, you can skip this option entirely, but you might lose voter trust if they find out that their votes are being transmitted unencrypted through the internet. You can also opt for a self-signed certificate which, although is better than HTTP, will still throw warnings for a browser.

That’s it!

A diagram with the complete infrastructure used by the PSHS-CVC SSG Election server.

If you were able to follow all those steps, then you’re 100% ready for an actual election using Halalan. The only thing left now is to add in the data through the administration panel, and to begin the elections yourself. For the SSG elections, I opted not to use the in-built results panel (since the candidates preferred to keep the actual count of votes hidden) and instead created my own live results dashboard using Grafana. Such a thing is out of the scope of this guide, however it’s not that hard to set up if you know how to deal with SQL and Grafana itself.

If you did happen to stumble upon a few issues, remember that Halalan is nearly 9-year-old software. It is expected to break at this point given changes to the other software and services that it depends on now. In case you do end up giving up and moving on, you can always turn to GitHub for some alternatives that might fit your use case.

The total cost of everything ended up at around US$ 3 and my sanity. Not bad, if I do say so myself.

That’s all for now. Good luck with getting your elections running!