Subversion Command Line Primer

Linux No Comments

Below is an aggregate of my notes on how to use command line Subversion. The syntax may not be 100% correct (may have changed in newer versions) so please adapt accordingly.

Note: For more details, refer to the free book, Version Control with Subversion, written by some of the Subversion developers.

Though I’m using Linux syntax below, the Subversion commands should also work on Windows and macOS.

# Show version installed
svn --version

# ------------ Local Admin ------------

# Create a repository
sudo mkdir /var/repos
sudo svnadmin create /var/repos

# Export a repository
svnadmin dump /var/repos > backup.svn

# Import a repository
svnadmin load /var/repos < backup.svn

# List all projects in the local repository.
svn list file:///var/repos

# ------------ Basic Remote Client ------------

# List all projects in the remote repository by svn+ssh or HTTP URI.
svn list svn+ssh://
svn --username <myuser> --password <mypassword> list

# Import a project into the repository
svn import ./myproject svn+ssh:// -m "Initial Import"

# Check out a local, working copy of a project from the repository
svn co svn+ssh:// ./myproject

# View the working copy's info (no need to input the URI once inside the project)
cd ./myproject
svn info

# Update the project to the latest version
svn update

# Add a new file
svn add newfile.cpp

# Remove a file
svn rm oldfile.cpp

# Move and/or rename a file
svn mv oldfile.cpp subdir/newoldfile.cpp

# Set file to be binary type
svn propset svn:mime-type 'application/octet-stream' myfile.bin

# Have Subversion ignore certain subdirectories or files
svn propedit svn:ignore .

# List the modified files/directories (aka status) made to working copy
svn status
svn st

# Show differences between local files and current revision (usually HEAD)
svn diff

# Commit (aka checkin) all local working changes to the repository
svn commit -m "first commit"
svn ci -m "first commit"

# Show checkin logs (revision number, user, time, checkin comment)
svn log

# List files modified by a particular revision
svn log -v -r <revision_number>

# Show line-by-line differences in a particular revision's changeset
svn diff -c <revision_number>

# ------------ Selective Commit ------------

# Create changelist for selective commit
svn changelist <changelist-name> file1 file2 file3

# Add or remove files from the selective changelist
svn changelist <changelist-name> --add file4
svn changelist <changelist-name> --remove file2

# Show differences in selective changelist
svn diff --changelist <changelist-name>

# Checkin only files listed in selective changelist
svn ci -m "comment" --changelist <changelist-name>

# ------------ Undelete a File ------------

# Find version when deletion took place
svn log verbose

# Undelete by copying back from revision (one prior to deletion revision) to current
svn copy --revision 835 .

# If get "Path not found", then add a revision suffix to the URI
svn copy ‐‐revision 835 .

# ------------ Switch to Previous Revision ------------

# Revert to previous revision
svn up -r [revision_number]

# Show files that have changed
svn st --show-updates

# diff against latest revision (diff by itself won't show any differences)
svn diff -r HEAD
# ------------ Branching and Merging ------------

# Find revision when branched from trunk
svn log -v --stop-on-copy

# Revert back to an earlier revision
svn merge -r 10340:10335 <file or dir>

# Merge just one file
svn merge -r10343:10739 ./myfile.cpp

# Merge from trunk to branch
svn merge --dry-run -r10810:HEAD .
svn merge -r10810:HEAD --accept mine-conflict .

# Merge from branch to trunk (automatically accept theirs-full if conflict)
svn merge --reintegrate --accept theirs-full .

# Update working copy to use a different branch
svn switch http://mydomain/myproject/branches/mybranch .
No Comments

Install LEMP on Ubuntu WSL on Windows 10

Windows Development No Comments

To facilitate web development, I wanted the same environment on my Windows 10 desktop as exists on my Ubuntu VPS server. With WSL (Windows Subsystem for Linux), I can have the same LEMP (Linux, Nginx, MySQL, and PHP) setup on my Windows 10 desktop.

See my previous post, Install Subversion on Ubuntu WSL on Windows 10, to get an Ubuntu-flavored WSL working on your Windows 10 system.

Instructions to install LEMP on Ubuntu WSL are almost the same as what I used to install LEMP on my Ubuntu server (see Install Ubuntu, LEMP, and WordPress on an Unmanaged VPS).

Install PHP

The latest Ubuntu WSL version is 18.04.1 LTS, which supports the latest PHP 7.2 version.

# Install PHP 7.2 and support packages:
# - php7.2-cli - PHP command line
# - php-mysql - PHP library to call MySQL database
# - php-fpm - PHP FastCGI Process Manager for Nginx integration
sudo apt install php7.2 php7.2-cli php7.2-mysql php7.2-fpm

# List the installed packages and grep for php-specific packages:
dpkg --get-selections | grep -i php

# Get the version of PHP installed
php -version

Install MySQL

# Install MySQL server.
sudo apt install mysql-server

# Get version of MySQL server and CLI installed
mysqld --version
mysql --version

# Start MySQL server
sudo service mysql start
sudo service mysql status

# Log into MySQL server as root user with blank password.
sudo mysql -u root
mysql> show databases;
mysql> quit

Note: Instead of using the “quit” command to exit the MySQL client, you can also use the “exit” command. Semi-colon termination (for example, “quit;” or “exit;”) are optional for these close connection commands.

By default, the MySQL server is configured to only allow root MySQL login from the root Linux user. Thus, we had to use “sudo mysql -u root” above. If we wish to allow a non-root Linux user to login using the root MySQL user (eliminate need for “sudo”), we’ll need to change how the root MySQL user is athenticated (switch to “mysql_native_password” plugin instead of the “auth_socket” plugin).

# Log into MySQL server as root user with blank password.
sudo mysql -u root

# Allow connection from a non-root Linux user
mysql> use mysql;
mysql> update user set plugin='mysql_native_password' where User='root';
mysql> flush privileges;

# Exit MySQL client.
mysql> quit

# Log into MySQL server as root user without "sudo".
mysql -u root
mysql> show databases;
mysql> quit

If you wish to secure the MySQL server, you can use the convenient “mysql_secure_installation” utility. Run the utility to enable password validation, set a root user password, remove anonymous users, disable remote root login, and remove the test database. You can re-run the utility again to reset the root user password if you need to.

# Run utility to secure MySQL server
sudo mysql_secure_installation

# Log into MySQL server as root user with password prompt
mysql -u root -p
mysql> show databases;
mysql> quit

If you wish to go back to using a blank MySQL root password, run the following to disable password validation and set a blank root password:

mysql -u root -p
mysql> uninstall plugin validate_password;
mysql> set PASSWORD for root@localhost=PASSWORD('');
mysql> quit

Install Nginx

# Install Nginx
sudo apt install nginx

# Start Nginx server
sudo service nginx start

Browse to http://localhost/ and you should see the initial Nginx welcome page.

Start the PHP-FPM service so Nginx can execute PHP scripts.

# Locate unix socket file which PHP-FPM service will listen to.
grep "listen =" /etc/php/7.2/fpm/pool.d/www.conf
# output: listen = /run/php/php7.2-fpm.sock

# Start PHP-FPM service.
sudo service php7.2-fpm start

Edit the default Nginx server block file to use the PHP-FPM service:

sudo nano /etc/nginx/sites-available/default

In the “default” server block file, change the following:

server {
        # Add index.php to the list if you are using PHP
        #index index.html index.htm index.nginx-debian.html;

        # Add index.php to front of "index" to execute it first
        index index.php index.html index.htm index.nginx-debian.html;

        # pass PHP scripts to FastCGI server
        #location ~ \.php$ {
        #       include snippets/fastcgi-php.conf;
        #       # With php-fpm (or other unix sockets):
        #       fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        #       # With php-cgi (or other tcp sockets):
        #       fastcgi_pass;

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;

                # Make sure unix socket path matches PHP-FPM configured path above
                fastcgi_pass unix:/run/php/php7.2-fpm.sock;

                # Prevent ERR_INCOMPLETE_CHUNKED_ENCODING when browser hangs on response
                fastcgi_buffering off;

Restart the Nginx service to have the changes take effect:

sudo service nginx restart

Test PHP and MySQL Integration

Create a PHP test script by running this edit command:

sudo nano /var/www/html/info.php

In the “info.php” file, input the following content:


Browse to http://localhost/info.php and you should see a page containing information about the PHP installation.

Create a PHP MySQL test script by running this edit command:

sudo nano /var/www/html/mysql.php

In the “mysql.php” file, input the following content:

// HTML response header
header('Content-type: text/plain');

// Database connection parameters
$DB_HOST = 'localhost';
$DB_PORT = 3306; // 3306 is default MySQL port
$DB_USER = 'root';
$DB_PASS = ''; // blank or password (if you set one)
$DB_NAME = 'mysql'; // database instance name

// Open connection (all args can be optional or NULL!)
$mysqli = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_PORT);
if ($mysqli->connect_error) {
  echo 'Connect Error (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error . PHP_EOL;
} else {
  // Query users
  if ($result = $mysqli->query('SELECT User FROM user')) {
    echo 'Database users are:' . PHP_EOL;
    for ($i = 0; $i < $result->num_rows; $i++) {
      $row = $result->fetch_assoc();
      echo $row['User'] . PHP_EOL;
  } else {
    echo 'Query failed' . PHP_EOL;

// Close connection

Browse to http://localhost/mysql.php and you should see a page listing the MySQL database users.

If you have problems, you can debug by enabling PHP-FPM error logging. Edit the configuration for the PHP-FPM pool:

# Edit www.conf file and add an error_log directive.
sudo nano /etc/php/7.2/fpm/pool.d/www.conf
    # Add an error log file location in the "/tmp" directory
    ;php_admin_value[error_log] = /var/log/fpm-php.www.log
    php_admin_value[error_log] = /tmp/fpm-php.log

# Restart PHP-FPM service for change to take effect
sudo service php7.2-fpm restart

Note: There exists a “/etc/php/7.2/fpm/php.ini” configuration file but that is not used by the Nginx PHP-FPM connection. Also, putting the error log file in a directory other than the “/tmp” directory (like “/var/log”) may not work due to permission issues.

Info above derived from:

No Comments

Install Subversion on Ubuntu WSL on Windows 10

Windows Development No Comments

I needed Subversion client access on a fresh Windows 10 installation. Rather than installing one of the Subversion clients for Windows (for example, CollabNet Subversion or SlikSVN), I decided to take advantage of the new WSL (Windows Subsystem for Linux).

The best part of macOS is the Linux system which macOS runs on. And now, the latest Windows 10 has something equivalent. Below are the steps I took to install WSL (Ubuntu flavor) and Subversion.

Install WSL Ubuntu

  1. Enable the WSL feature.
    • Run “Turn Windows features on or off” and check the box for the “Windows Subsystem for Linux” feature.
    • Alternatively, launch the Windows PowerShell as an administrator and run this command:
      Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
  2. Restart the computer to finish enabling the WSL feature.
  3. Launch the “Windows Store” and search for “Run Linux on Windows”.
    • Click on Ubuntu (or your favorite flavor of Linux).
    • Click on Get (or Install) to install Ubuntu.
  4. Run the installed “Ubuntu” app to configure the WSL.
    • After several minutes, it will prompt you to input a Unix username and password for your default sudo user. (This will be the default user that your bash shell runs under.)
  5. You can run “Ubuntu” or “bash” to launch the bash terminal shell.
    • “Ubuntu” will put you in the “/home/<username>” directory.
    • “bash” will keep the current directory.
  6. Some Linux commands to try out:
    # Show version of Linux
    lsb_release -a

    # Show HOME variable and current directory
    echo $HOME

    # Two ways to list the contents of the "C:\Program Files" directory
    ls "/mnt/c/Program Files"
    ls /mnt/c/Program\ Files

    # Log into root user
    sudo su

While it is easy to access the Windows drive from Ubuntu by using “/mnt/c” for the C:\ drive, the Linux root “/” directory maps to the Windows directory at “C:\Users\<username>\AppData\Local

I recommend creating a NTFS symbolic link to make it easier to access the Linux home directory. Open a Command Prompt as administrator and run this command:

mklink /d c:\ubuntu "C:\Users\<username>\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_<UID>\LocalState\rootfs"

Note: Strangely, if I create a new file in the Ubuntu’s rootfs directory using Windows, the file is not visible under the Ubuntu shell. Editing an existing file from Windows works fine.

Install Subversion

Installing Subversion on WSL Ubuntu is identical to installing Subversion on my unmanaged Ubuntu virtual server (see Subversion Over SSH on an Unmanaged VPS).

Note: You may notice the switch from the “apt-get” command to the “apt” command when installing and updating Ubuntu packages. The “apt” command is a slightly smaller subset of the “apt-get” command, more user-friendly, and appears to be the recommended command to use going forward.

Below are the steps I took to update Ubuntu, install Subversion, and connect to the remote Subversion repository on my server.

  1. Update Ubuntu packages using the bash shell:
    # Refresh the APT repository index
    # If you don't update, the install command below will fail.
    sudo apt update

    # List packages that can be upgraded
    apt list --upgradable

    # Upgrade packages with auto-handling of dependencies
    # (same as "apt-get dist-upgrade")
    sudo apt full-upgrade

    # Remove dependencies which are no longer used (frees up space)
    sudo apt autoremove
  2. Install Subversion:
    # Install subversion
    sudo apt install subversion

    # Check that subversion is installed
    svn --version
  3. Because my server uses a custom SSH port and requires the Subversion client to use the SVN+SSH protocol, we must configure SSH to use the custom port automatically because there is no option to input the custom port on the command line.
    # Create .ssh configuration directory under the home directory
    mkdir ~/.ssh

    # Optionally, restrict access to the directory to just the user/owner
    chmod 700 ~/.ssh

    # Create SSH config file
    cat > ~/.ssh/config
      Port 3333
      PreferredAuthentications publickey,password
    # Press CTRL-D to save and exit the file

    # SSH requires that only the user has access to files under the ~/.ssh directory.
    # If you don't restrict, SSH will throw a "Bad owner or permissions" warning and ignore the files.
    chmod 600 ~/.ssh/config

    # Copy the trusted client "id_rsa" and "" identity files to the ~/.ssh directory
    # to eliminate the need to input the password when using SSH.
  4. Connect to the remote Subversion repository:
    # List all projects in the remote repository.
    svn list svn+ssh://

    # Check out a local, working copy of the project from the repository
    svn co svn+ssh:// ./myproject

    # View the working copy's info (no need to input the svn+ssh URL once inside the project)
    cd ./myproject
    svn info

See my followup post, Install LEMP on Ubuntu WSL on Windows 10, for instructions on getting a full LEMP (Linux, Nginx, MySQL, PHP) development environment working on Ubuntu WSL.

Info above derived from:

No Comments

NTFS BitLocker Access on macOS 10.13 High Sierra

Mac OS X No Comments

I had a flash drive with important info that I needed to access. Unfortunately, I only had a Mac computer and the flash drive was protected by Windows BitLocker encryption.

A quick google pointed me at “M3 Bitlocker Loader for Mac”, which looked to be free. Unfortunately, it was not. The utility mounted the flash drive, prompted me for a password, and unencrypted the drive successfully. I was able to see the files but when I attempted to read or copy, the utility said that I needed to buy it for $40. Kinda expensive but if someone is desperate….

Thankfully there is a free solution called “Dislocker fuse”. Installation is easy using the HomeBrew package manager. Below are the steps I took to mount the flash drive.

Install “Dislocker fuse”

  1. Install Xcode from the Mac App Store.
  2. Install the “Xcode Command Line Tools” by running this command in the “”:
    xcode-select --install

    Note: If you skip this step, the Homebrew installation will install multiple versions of the “Xcode Command Line Tools” (for different OS and Xcode versions) and then hang.

  3. Install Homebrew:
    /usr/bin/ruby -e "$(curl -fsSL"
  4. Install Fuse for macOS (which Dislocker requires):
    brew cask install osxfuse
  5. Reboot the Mac to ensure that “Fuse for macOS” is fully installed.
  6. Install Dislocker:
    brew install dislocker

Mount and Decrypt the Flash Drive

  1. Plug your USB flash drive into the Mac. Click the Ignore button on the “The disk you inserted was not readable by this computer” dialog box.
  2. Get the identifier for the BitLocker partition on the flash drive:
    diskutil list

    /dev/disk2 (external, physical):
       #:                       TYPE NAME                    SIZE       IDENTIFIER
       0:     FDisk_partition_scheme                        *61.9 GB    disk2
       1:               Windows_NTFS                         61.9 GB    disk2s1

    Note: The identifier for my flash drive partition (shown on far right above) is “disk2s1”. If you don’t use the correct identifier, you will get a “The signature of the volume (м) doesn’t match the BitLocker’s ones (-FVE-FS- or MSWIN4.1)” error when attempting to unlock the BitLocker partition below.

  3. Unlock and mount the partition:
    # Unlock the BitLocker partition to a Dislocker virtual partition file
    sudo dislocker -v -V /dev/disk2s1 -r -uPASSWORD /tmp/mydrive

    # Mount the unlocked virtual partition
    sudo hdiutil attach /tmp/mydrive/dislocker-file -imagekey diskimage-class=CRawDiskImage -mountpoint /Volumes/mydrive

    – Replace PASSWORD with your password. Make sure to escape any special characters like exclamation mark with a backslash (ex: “secret\!”).
    – The “-r” flag passed to Dislocker allows only read-only access. When I omitted the flag to get read-write access, the dislocker command failed.

  4. Later, unmount the drive by doing the following:
    # Unmount the unlocked virtual partition
    sudo hdiutil detach /Volumes/mydrive

    # Release the virtual partition
    sudo hdiutil detach /tmp/mydrive

Info above gotten from Open Bitlocker USB stick on OS X.

No Comments

Migrate Post From One WordPress Database To Another

Internet No Comments

I made a mistake and posted a blog entry to the wrong WordPress website. A comment was made before I could manually delete and post it to the other website. While I could just lose that comment, I decided it would be interesting to see if I could manually migrate the post content (with comments) directly from one WordPress MySQL database to the other.

After looking at the WordPress database schema, I found that I only needed to pull data from two tables and modify a few column values.

These are the essentials to keep in mind when migrating a post:

  • The two “wp_posts” and “wp_comments” tables contain the data we need to migrate.
  • For “wp_posts”, we need to modify columns “ID”, “post_author” (foreign key to “wp_users” table), and “guid”.
  • For “wp_comments”, we need to modify columns “comment_ID”, “comment_post_ID”, and “comment_parent” (references “comment_ID” for replies).
  • We can ignore tables “wp_postmeta” (contains edit lock info) and “wp_commentmeta” (contains Akismet approval data).
  • No need to deal with categories and tags (kept in “wp_term_taxonomy”, “wp_termmeta”, and “wp_terms” tables). We can manually set category (from the default “Uncategorized”) and insert tags after the migration.
  • Image links in the post will point back at the old WordPress site, so we will need to add the images to the new WordPress site and update the image URLs accordingly.

Before we start, make sure that both the WordPress websites are updated to the same version. (My websites are using the latest WordPress version 4.9.7.)

Read Post From Old WordPress Database

Identify the post identifier that you wish to move. If you click on your post header, the URL will change to include the identifier. For example, my URL is “” and the post identifier is 3048.

# SSH into your server and log into MySQL.
mysql -u root -p
    # Input your MySQL root password if it is not blank

# Select the <database_name> to read from.
mysql> use <database_name>;

# By default, MySQL is configured to only support reading/writing files from a "mysql-files" directory.
# You could remove this requirement, but I decided to just make use it.
mysql> show variables like 'secure_file_priv';
| Variable_name    | Value                 |
| secure_file_priv | /var/lib/mysql-files/ |

# Output the post content matching <post_id> to a file.
mysql> select * from wp_posts where ID=<post_id> into outfile '/var/lib/mysql-files/post.txt';

# Output the post comments matching <post_id> to a file.
mysql> select * from wp_comments where comment_post_ID=<post_id> into outfile '/var/lib/mysql-files/comments.txt';

Insert Post Into New WordPress Database

While we could modify the column data in the output files, I decided to do manual overwrites when importing. (You will need to be the root user in order to access “/var/lib/mysql-files/” directory.)

# Select the <other_database_name> to write to.
mysql> use <other_database_name>;

# WordPress orders posts by publish date so insert using next auto-increment post id.
# You'll need this to modify the "guid" string below.
mysql> select max(ID)+1 from wp_posts;

# Find a user ID to use as the "post_author".
mysql> select ID,user_login,display_name from wp_users;

# Insert the post into the other database.
# Set ID as NULL so the next auto-increment value will be used.
# Select existing <my_user_id> as post_author.
# Update guid with other WordPress domain and the next auto-increment post id.
mysql> load data infile '/var/lib/mysql-files/post.txt' into table wp_posts set ID=NULL, post_author=<my_user_id>, guid="http://www.<other_domain>.com/?p=<auto_incremended_post_id>";

Inserting comments is problematic because reply comments require the identifier for the parent comment, which doesn’t exist yet.

One solution is to lookup the next auto-incremented comment ID and then edit the “comments.txt” outfile accordingly before importing. However, because my website is active, some comments could be inserted before I do the import, making all the identifiers in the outfile invalid.

The second solution, which I chose, is to import the comments and then manually fix the “comment_parent” identifiers (and the “user_id” which is nonzero for users in “wp_users”).

# Insert the comments into the other database.
# Set comment_ID as NULL so the next auto-increment value will be used.
# Set the comment_post_ID to be our <auto_incremented_post_id> from above.
mysql> load data infile '/var/lib/mysql-files/comments.txt' into table wp_comments set comment_ID=NULL, comment_post_ID=<auto_incremented_post_id>;

# Lookup the newly-inserted comments.
mysql> select comment_ID,comment_author,comment_author_email,comment_parent,user_id from wp_comments where comment_post_ID=<auto_incremented_post_id>;

# Update comment_parent to the correct comment_ID (replace 472365 and 472366 below accordingly).
mysql> update wp_comments set comment_parent=472365 where comment_ID=472366;

# Update user_id for reply comments made by you (replace <my_user_id> and 472366 below accordingly).
mysql> update wp_comments set user_id=<my_user_id> where comment_ID=472366;

Fix Category, Tags, and Images

The post and comments should now be visible on your other WordPress website. You can set the post’s category and tags using the normal editing process. And add any images and update the image URLs in the post body accordingly.

Once you’re confident that the post is displayed correctly, you can delete its duplicate (and related images) from the old WordPress website. Also, you can delete the no longer necessary outfiles from the “/var/lib/mysql-files” directory.

And finally, for completeness, set a redirection from the old post URL to the new post URL.

Some info above gotten from:

No Comments

Fix GPS on iPhone 6s

Mobile Devices No Comments

Two months ago, Google Maps stopped telling me directions when navigating on my Apple iPhone 6s. I could still navigate without the voice prompts though. Two week ago, my current location would only update once per minute. I couldn’t navigate at all. Sometimes, after 5-10 minutes, the GPS location updates would start working correctly again. In any case, my GPS was broken. (Apple Maps exhibited the same GPS problem.)

Note: The intermittent GPS updates reminded me of the refurbished iPhone 5s I got from T-Mobile. It would abruptly lose power in very cold weather. Later I found out that the phone was missing parts inside, specifically, the metal shield over the battery connector; so during cold weather, some parts must have contracted and shorted the battery out. Maybe my GPS problem was similar. Alas, no parts were missing from the iPhone 6s.

I thought it was a software bug because I had updated to iOS 11.2 near the time the GPS started misbehaving. Updating to iOS 11.3 did not fix the GPS problem. I reset the location configuration (under Settings, General, Reset, Reset Location & Privacy), did a “Reset All Settings”, and performed a full system restore (using iTunes), but none of those actions fixed the GPS.

After researching, I could only conclude that the GPS issue was a hardware problem. Most likely, the GPS antenna needed to be replaced.

Tip: If you no longer hear the Google Maps (or Apple Maps) navigational voice prompts, then the iPhone probably was not able successfully to acquire the GPS satellites. However, Google Maps will still roughly (and inaccurately) update your position using the cellular, Wi-Fi, and Bluetooth networks.

While there were numerous instructions on replacing the iPhone 6 GPS antenna, I could only find one Youtube video (from the Netherlands) that had instructions on replacing the iPhone 6s GPS antenna.

The reason is that while the iPhone 6 had a separate GPS antenna, the iPhone 6s uses an integrated cellular, Wi-Fi, and GPS antenna. Unfortunately, different sources refer to the antenna by different names. iFixit calls it the cellular antenna. Some Youtube videos call it the Wi-Fi antenna. Many eBay sellers call it the antenna “retaining bracket” which doesn’t sound like an antenna at all.

Note: To add to the confusion, there is a second Wi-Fi antenna which iFixit calls the “Wi-Fi diversity antenna”. I suppose it is necessary to increase the Wi-Fi reception range.

I ended up purchasing a used, original iPhone 6s antenna by mistake. I was looking for an authentic iPhone 6s antenna (instead of the cheaper “OEM” clones), found one for a reasonable price (almost twice as expensive as the OEM), thought it was new, and purchased it. I installed the antenna by following instructions from the Youtube video above and iFixit’s iPhone 6s Cellular Antenna Replacement page.

Once the antenna was replaced and after testing, I found that the GPS was still not working correctly. While I didn’t see the intermittent GPS location update issue, it would still take several minutes before Google Maps would start updating the location correctly.

Note: Once the GPS was working, I did noticed that in Google Maps (and Apple Maps), the shadow on the location dot pointed in the wrong direction. It was 80 degrees off from my car’s direction of travel. Strangely, the directional shadow was correct when Google Maps was giving walking directions.

Some forums recommended installing an iOS app called GPS Diagnostic: Satellite Test (available for $2.99). The app provided more information on the GPS function and indicated that it was taking several minutes before the phone could acquire the GPS satellites.

Tip: Test the GPS outside with an unobstructed sky. The phone will have problems acquiring the GPS Satellites inside a house.

In conclusion, I purchased a brand new iPhone 6s OEM antenna, installed it, walked outside, opened up the GPS Diagnostics app, and within a few seconds, the phone successfully acquired the GPS satellites. I’ve tested the GPS navigation a couple of times since then and Google Maps worked immediately as expected. The directional shadow was still wrong, but hopefully it will fix itself over time.

Update: Sporadically, the GPS would not be able to acquire the satellite when I first turn it on. I found out that it was because I hadn’t tightened the screws on the GPS antenna enough. Unlike the screws on the other parts (like the metal shields which you don’t need to tighten too much), the screws on the antenna need to be tight to ensure a solid metal connection.

I noticed another benefit. Wi-Fi reception is poor in a corner of my house. With the old antenna, the iPhone wouldn’t switch to the LTE network, but would just keep trying and failing to use the Wi-Fi. With the new OEM antenna, the iPhone switches quickly to the LTE network (and back).

I hope the above will help you to fix your iPhone 6s’ GPS woes.

No Comments

You May Not Have the Latest Windows 10 Version

Windows No Comments

I found an issue with my Windows 10 desktop. Unknown to me, it has been running an old version of Windows 10 and is lacking all of the recent security updates. Worse, when I run “Check for updates” (a.k.a. Windows Update), Windows say that it is “up to date”. But Windows 10 lies.

I remember Windows Update complaining about a failed update and asking me to reboot several months ago. After several reboots, Windows stopped complaining so I assumed the problem was solved. But it wasn’t. Windows had given up and then pretended that everything was hunky-dory.

Running “winver” tells me that my desktop’s Windows 10 version is 1703. The latest version is 1709, known as the Fall Creator’s Update. When I check my update history (under Windows Update), it says that the version 1709 update has “failed to install”. There used to be a link to reboot so Windows could try again, but that link is gone.

Update Assistant Required

I check the web and there are all sorts of solutions recommending running sfc (and dism), stopping services, clearing out the update history, etc. I try several but they did not fix the problem. The real problem is that Windows Update is not capable of upgrading to version 1709. Version 1709 requires that we install and run the Windows 10 Update Assistant utility.

The Windows 10 Update Assistant downloads the version 1709 update, installs it, and requests a reboot. (Even though I gave permission to reboot, nothing happened but the Update Assistant quitting. I had to do a manual reboot.) On boot up, a grey screen is shown with a message that the update will take awhile. On my Intel Core i5 system with SSD, it took around 20 minutes to complete the update.

The “winver” utility now says that the version is 1709. Windows Update now indicates that “updates are available” and starts downloading them for installation. (There are about 5 updates including “Definition Update for Windows Defender Antivirus”.)

Cleanup Old Windows Version

The old version of windows is kept under “C:\Windows.old” and takes up a lot of space (22.6GB on my system). The directory is classified as temporary files, so there are several ways to delete it.

  • Run “Disk Cleanup”, select the “C:\” drive, click on “Clean up system files” button on the bottom-left, select “C:\” drive again, check “Previous Windows installation(s) 23.9GB” and “Windows Update Cleanup 1.78GB”, click OK, and confirm by clicking “Delete Files”.
  • Run “Storage”, click on “This PC (C:)”, click on “Temporary files”, check “Previous version of Windows”, and click “Remove files”.
  • Manually deleting the directory.

Note: Either the Disk Cleanup or Storage may freeze when deleting the “Windows Update Cleanup”. This happened on my system. After waiting 20 minutes, I cancelled the cleanup and restarted. The second time through, the operation completed within several seconds.

I recommend uninstalling the “Windows 10 Update Assistant” using the “Add and remove programs”. It is no longer needed. When the next major version update is required, you will want to download the latest version of the Update Assistant then. Uninstalling Update Assistant will delete the “C:\Windows10Upgrade” directory (20MB).

Note: On my desktop, the Windows 10 Update Assistant insists on launching at startup to thank me for updating with a truncated text message. Worse, the close window icon (upper-right X mark) is greyed out. Worst, it places an icon in the system tray. I don’t recall if the system tray icon has a menu option to quit or not.

Check your Windows 10 version and if necessary, update to the latest 1709 version with the Windows 10 Update Assistant.

No Comments

Apple iTunes, A Hard Drive Space Hog

Windows No Comments

My Windows laptop complains that there is very little free space left on the 128GB SSD drive. That’s strange because Windows 10 and the few applications I installed shouldn’t have taken that much space, maybe at most half the hard drive space. I dig around and find that my user’s hidden AppData directory is taking up 47GB!

Further investigation reveals that the culprit is “%APPDATA%\Apple Computer”, belonging to the Apples iTunes application. Specifically, iTunes’ device backup directory uses 34GB and software update directories use 12GB.

I use iTunes to backup and update several iPhones and iPads belonging to myself and different family members. Over the years, there have been many backups and many iOS versions. Because the base memory on the newer iPhone and iPad has increased (from 16GB to 32GB or higher), their backup images have grown in size (more songs, photos, and videos). Worse, iTunes does not automatically delete old iOS images that are no longer in use.

Tip: Windows 10 (and probably Windows 8) includes a Systems Settings app called “Storage” that helps you to view and drill down into the space usage. Run it and select the drive to view how much space each file category is using. Click on the category to drill down further.

Below are instructions on how to free up the drive space on Windows.

Delete Device Backups

To delete the backups using iTunes:

  1. Browse to menu “Edit->Preferences…”.
  2. Click on “Devices” (top-right icon).
  3. Select the unwanted backup listed under “Device backups:” and click “Delete Backup”.

Tip: Look for duplicate device backups to delete. Usually the device’s old backup is overwritten by the new backup; however, sometimes iTunes won’t overwrite and will create a new backup instead.

iTunes will move the deleted backups to the “%LOCALAPPDATA%\Temp” directory so you will want to clean up that directory manually or by using the free CCleaner tool. (Unfortunately, Windows’ built-in “Disk Cleanup” app does not empty the “%LOCALAPPDATA%\Temp” directory.)

Alternatively, you can manually delete all the backups using the command line:

# Change directory to the device backup directory
cd "%APPDATA%\Apple Computer\MobileSync\Backup"

# Delete all subdirectories under device backup directory
for /d %d in (*) do rmdir /s %d

# You will be prompted to confirm deletion for each subdirectory.
# Note: We can pass a "/q" quiet flag to "rmdir" to not prompt, but that is dangerous!

Delete iOS Images

Delete the iOS images using the command line below. iTunes will download them again in the future if necessary.

# Delete iPad iOS images (ex: iPad3,2_9.3.5_13G36_Restore.ipsw)
cd "%APPDATA%\Apple Computer\iTunes\iPad Software Updates"
del *.ipsw

# Delete iPhone iOS images (ex: iPhone_4.0_64bit_10.3.1_14E304_Restore.ipsw)
cd "%APPDATA%\Apple Computer\iTunes\iPhone Software Updates"
del *.ipsw

# Delete iPod iOS images (ex: iPod4,1_6.1.6_10B500_Restore.ipsw)
cd "%APPDATA%\Apple Computer\iTunes\iPod Software Updates"
del *.ipsw

Note: iTunes 12.6.2 and earlier versions would also backup the apps, taking up more space than later versions. Later versions will re-download the apps from the App Store on the device when restoring.

If you use iTunes to backup and update iOS devices and find that your computer is running out of drive space, you may be suffering from the same issue that I have above. I hope that the above info will help you to slay your iTunes space hog.

Tip: Instructions are similar for Macs. Look in the “~/Library/Application Support/MobileSync/Backup” and “~/Library/iTunes” directories.

No Comments

Update to Latest Ubuntu Kernel

Linux No Comments

Recently I got an email from DigitalOcean to update my server’s kernel to protect against the Meltdown and Spectre exploits. The instructions are the normal update commands that I usually run, “apt-get update” and “apt-get dist-upgrade”.

After doing the update, I thought that I would double-check the kernel version. The latest Ubuntu 14.04 kernel is “3.13.0-143-generic”. The kernel that my server was using is “3.13.0-039-generic”. My server was running an extremely old version of the kernel.

Supposedly, the Ubuntu GrubLoader should automatically run the latest installed kernel version after a reboot. However, my server was not doing that. It appears to be an issue with DigitalOcean’s droplet configuration.

The workaround is to use the DigitalOcean web interface to change the kernel from “DigitalOcean GrubLoader v0.2 (20160714)” to “DigitalOcean GrubLoader v0.1 (20160527)” (or vice versa). Then shutdown the server (using the command line) and switch the droplet back on using the DigitalOcean web interface. Somehow, changing the GrubLoader version makes the GrubLoader start working properly again.

Here are the full instructions to ensure that your Ubuntu server is running the latest kernel:

# Show Ubuntu version
lsb_release -a

# Show kernel version (alternatively, run "uname -a" or "cat /proc/version")
uname -r

# Update apt-get repository
sudo apt-get update

# Install any updates, handle dependencies automatically
sudo apt-get dist-upgrade

# Reboot so Grub will load any newly-installed kernel version
sudo reboot

# After reboot, show kernel version
uname -r

# If the kernel version is not the latest "3.13.0-143-generic" or later,
# go to the DigitalOcean web interface and switch the droplet's kernel
# from "DigitalOcean GrubLoader v0.2" to "DigitalOcean GrubLoader v0.1"
# (or vice versa).

# Doublecheck by listing the installed kernel image(s)
dpkg --list | grep linux-image-generic

# Shutdown the server
sudo poweroff

# On the DigitalOcean web interface, refresh the Droplet details until
# you see the power switch (located to right of Droplet name) go to
# the Off position.  Switch it back to the On position.

# After server boots up, show kernel version (you should see latest)
uname -r

# If you can't ssh into your server or you want the later
# "DigitalOcean GrubLoader v0.2" version, just repeat the steps above
# to change the GrubLoader version.

# Remove dependencies and packages which are no longer used
sudo apt-get autoremove

# If you get a "you may need to re-run your boot loader[grub]" warning,
# optionally update Grub's configuration.
sudo update-grub

Info above derived from:

No Comments

Re-subscribe to Let’s Encrypt Renewal Reminder Emails

Linux No Comments

Let’s Encrypt will automatically email expiration notices when your domain’s SSL certificate is coming up for renewal at 20 days, 10 days, and 1 day before the expiration date. (Recently, I received notices at 19 days and again at 10 days.)

I’ve found these renewal reminders very helpful because in the past, they’ve told me that the certificate auto-renewal process I had created (see Free SSL Certificate from Let’s Encrypt for Nginx) was broken. (Initially, I had attempted to setup the auto-renewal process to execute as a non-root user, but frequent updates requiring root access kept breaking it. I ended up configuring the auto-renewal to use root access.)

The renewal email body contains a link with the title “If you want to stop receiving all email from this address, click…” at the end. Unfortunately, the link is very long, taking up 3 to 4 lines of text (on my screen) and making it easy to click on by accident. More unfortunate, clicking on the link will disable the sending of expiration reminders to your email address for all domain certificates, not just the particular domain certificate in question. This change cannot be undone; you cannot re-register the same email address.

However, there is a re-registration workaround documented at Let’s Encrypt’s Expiration Emails page. The workaround takes advantage of how most email services will ignore the plus symbol and whatever follows it in an email address. For example, “” is treated the same as “”.

To re-subscribe your email address, run the command below in your certbot installation directory. (You will be prompted to input your sudo password if necessary.)

$ cd certbot
$ ./certbot-auto register --update-registration --email

Requesting to rerun ./certbot-auto with root privileges...
Saving debug log to /var/log/letsencrypt/letsencrypt.log

Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'
d like to send you email about EFF and
our work to encrypt the web, protect its users and defend digital rights.
(Y)es/(N)o: N

 - Your e-mail address was updated to

The community support page, Accidentally Unsubscribed, mentions an alternative workaround using appears to be an external website which periodically checks the expiration date on your domain’s SSL certificate. I haven’t tried it but I don’t see any reason why it wouldn’t work.

No Comments

« Previous Entries