Best iPhone Case?

OK this is very subjective but I’ve used this for my iPhone 14 and this case for an iPhone 13 Mini in the last 2 years. They are considerably cheaper than the Apple equivalent and both worked fine with my Belkin iPhone Magsafe Charger (I actually have the non-watch version which you can’t get anymore it seems).

Apple certainly make great products but they are not cheap and neither are the accessories.

Featured products:

Spigen iPhone 14 Magsafe Case

Spigen iPhone 13 Mini Magsafe Case

Belkin iPhone Magsafe Charger

The Benefits of Website Monitoring

There are many tools out there to help with various aspects of running a website. One which, until recently, I had neglected is an external website monitor (I used my own polling scripts, which was time consuming and fiddly). Even with regular server maintenance and updates problems can still occur:

  • Memory leakage
  • Disk space running out
  • Network outages
  • Malicious activity

I’m sure there are lots of options out there but I’m currently using UptimeRobot as they provide an excellent free service to get you going. This provides basic HTTP monitoring and page keyword monitoring for up to 50 websites.

The paid options seem reasonable compared to similar services from other companies and add features like SSL certificate monitoring and domain expiry monitoring. They also provide Apple and Android apps. I have the Apple one and it seems to work well.

Lesser Spotted SQL for Rails 7 (and MySQL)

I recently migrated a Rails 4 application to Rails 7. On the whole it wasn’t too bad but today I was trying to add a child table to the data schema – a simple create_table… When I attempted to “db:migrate” I got an error:

$ rails db:migrate
== 20220520112518 CreateArticleTexts: migrating ===============================
-- create_table(:article_texts)
rails aborted!
StandardError: An error has occurred, all later migrations canceled:

Column `article_id` on table `article_texts` does not match column `id` on `articles`, which has type `int(11)`. To resolve this issue, change the type of the `article_id` column on `article_texts` to be :integer. (For example `t.integer :article_id`).
Original message: Mysql2::Error: Cannot add foreign key constraint
/home/myproject/db/migrate/20220520112518_create_article_texts.rb:3:in `change'

Caused by:
ActiveRecord::MismatchedForeignKey: Column `article_id` on table `article_texts` does not match column `id` on `articles`, which has type `int(11)`. To resolve this issue, change the type of the `article_id` column on `article_texts` to be :integer. (For example `t.integer :article_id`).
Original message: Mysql2::Error: Cannot add foreign key constraint
/home/myproject/db/migrate/20220520112518_create_article_texts.rb:3:in `change'

Caused by:
Mysql2::Error: Cannot add foreign key constraint
/home/myproject/db/migrate/20220520112518_create_article_texts.rb:3:in `change'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)

My old schema (from Rails 4 remember) used :int for the primary key data type. Rails 7 (and Rails 6 – I tested it) uses :bigint for the primary key data type.

In order to create a new child table I first need to upgrade the primary key on the parent table. After much trial and error the migration below is what I came up with for MySQL. In this example I also had 2 other legacy child tables which needed updating also so that foreign keys and primary keys had matching data types:

class ArticleUpdateId < ActiveRecord::Migration[7.0]
  #
  # For Rails 6+ we need legacy tables to have primary keys updated from :int to :bigint
  # before we can add any new child tables. The migrations below are for MySQL.
  #

  def up
    # drop primary key index and foreign key constraints otherwise we cannot make changes
    execute('ALTER TABLE article_filters DROP FOREIGN KEY fk_rails_303034c0ef')
    execute('ALTER TABLE article_keywords DROP FOREIGN KEY fk_rails_251b14e14a')
    execute('ALTER TABLE articles CHANGE id id int') # first remove AUTO_INCREMENT
    execute('ALTER TABLE articles DROP PRIMARY KEY')

    # old article_id child fields did not use bigint
    execute('ALTER TABLE article_filters CHANGE article_id article_id bigint NOT NULL')
    execute('ALTER TABLE article_keywords CHANGE article_id article_id bigint NOT NULL')

    # old id fields did not use bigint
    execute('ALTER TABLE articles CHANGE id id bigint PRIMARY KEY AUTO_INCREMENT')

    # recreate foreign key constraints
    execute 'ALTER TABLE article_filters ADD CONSTRAINT fk_rails_303034c0ef FOREIGN KEY (article_id) REFERENCES articles (id)'
    execute 'ALTER TABLE article_keywords ADD CONSTRAINT fk_rails_251b14e14a FOREIGN KEY (article_id) REFERENCES articles (id)'
  end

  def down
    # drop primary key index and foreign key constraints otherwise we cannot make changes
    execute('ALTER TABLE article_filters DROP FOREIGN KEY fk_rails_303034c0ef')
    execute('ALTER TABLE article_keywords DROP FOREIGN KEY fk_rails_251b14e14a')
    execute('ALTER TABLE articles CHANGE id id bigint') # first remove AUTO_INCREMENT
    execute('ALTER TABLE articles DROP PRIMARY KEY')

    # back to int on child table references
    execute('ALTER TABLE article_filters CHANGE article_id article_id int NOT NULL')
    execute('ALTER TABLE article_keywords CHANGE article_id article_id int NOT NULL')

    # back to int on parent table
    execute('ALTER TABLE articles CHANGE id id int PRIMARY KEY AUTO_INCREMENT')

    # recreate foreign key constraints
    execute 'ALTER TABLE article_filters ADD CONSTRAINT fk_rails_303034c0ef FOREIGN KEY (article_id) REFERENCES articles (id)'
    execute 'ALTER TABLE article_keywords ADD CONSTRAINT fk_rails_251b14e14a FOREIGN KEY (article_id) REFERENCES articles (id)'
  end
end

This seems to work well so far. Neither ID nor auto_increment values were affected by the migration and my parent id and child article_id columns are now bigint.

Resuming File Transfers in RSync

RSync is a great tool for archiving and backup. But how do you get it to cope with connection problems? For example, you are trying to backup a large archive file but you have a poor connection which keeps dropping and so your transfer has to start all over again. Well here’s what I use:

rsync -avz --progress --partial -e ssh /home/mybackup/* <email address>@<rsync host>:<remote folder name>

Note that the email address does not require quote marks around it. Notice I do not use –append or –append-verify. If I did it would assume that the start of the file has remained the same (which might be the case for a log file or similar). This isn’t the case for me. I also don’t use the –delete option because this would simply delete the temporary file left by –partial.

It works a treat but I would recommend a break of 15 minutes when attempting to resume the transfer (the time will vary depending upon how the remote server deals with temporary files).

For more on this and a very useful bash script, have a look here:
http://superuser.com/questions/302842/resume-rsync…

Rails 5 + Puma + Nginx + ActionCable

In development mode things seemed to take care of themselves. Production mode was a different matter. Let’s just say it was a sensitive and delicate configuration. So in documenting this I’ve tried to condense things to the bare minimum and using the simplest configuration: hosting the “cable” within the host application (i.e. in the same virtual host). For full ActionCable documentation please visit: Rails Guides

Make sure you have a cable.js file (app/assets/javascripts/cable.js)

The “rails new” command should have created this for you.

Create a handler (app/assets/javascripts/channels/chat_channel.coffee)

App.chat = App.cable.subscriptions.create "ChatChannel",
  connected: ->
    alert 'connected'

  disconnected: ->
    alert 'disconnected'

  received: (data) ->
    # do you screen update stuff here

I’ve included popup alerts. These are quite useful when you’re doing this for the first time so you know your basic connection is working.

Make sure you have your channel and connection ActionCable files

Check for app/channels/application_cable/channel.rb and app/channels/application_cable/connection.rb. These should have been created for you. If you’re running ActionCable within your application (rather than on a separate server/vhost) you can add authentication for the connection thus:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    protected
    def find_verified_user
      if current_user = User.find_by(id: cookies.signed[:user_id])
        current_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

Create your own channel file (app/channels/chat_channel.rb)

Here’s where you subscribe to your own broadcast.

class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from 'chat_channel'
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end

Set your configuration parameters

There are 2 files to change. First we must update config/environments/production.rb and set the config.action_cable constants:

config.action_cable.url = 'ws://myapp.spannersoftware.com/cable' # ws:// is non-secure, wss:// is secure
config.action_cable.allowed_request_origins = [ 'http://myapp.spannersoftware.com' ]

Important: do not add a trailing slash to either of these!

Next update config/cable.yml and set the host and port for the redis server:

development:
  adapter: async

test:
  adapter: async

production:
  adapter: redis
  url: redis://localhost:6379/1

You may not need to change this.

Add the ActionCable metatag

In app/views/layouts/application.html.erb add the following line in the <head> section:

<%= action_cable_meta_tag %>

Install and fire up the Redis server

Redis is a cool bit of kit for providing efficient messaging, for more information go here: Redis.io.

apt-get install redis-server
service redis-server start

Edit /etc/redis/redis.conf to set the port to 6379 and bind to 127.0.0.1.

Configure Nginx and Puma

Here is my Nginx virtual host example:

upstream my_app {
 server unix:/home/myapp/tmp/myapp.sock;
}
server {
 listen 10.10.10.10:80;
 server_name myapp.spannersoftware.com;

 access_log /home/myapp/log/access.log;
 error_log /home/myapp/log/error.log;
 root /home/myapp/public;

 location / {
 try_files /maint.html $uri @ruby;
 }

 location @ruby {
 proxy_pass http://my_app;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header Host $http_host;
 proxy_redirect off;
 }

 location /cable {
 proxy_pass http://my_app;
 proxy_http_version 1.1;
 proxy_set_header Upgrade $http_upgrade;
 proxy_set_header Connection "upgrade";
 }
}

Here is my Puma config (config/puma.rb):

threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
bind "unix:/home/myapp/tmp/bluebird2.sock"
environment ENV.fetch("RAILS_ENV") { "production" }
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
daemonize true
pidfile '/home/myapp/tmp/pids/puma.pid'
I'm using a Unix socket here, the default environment is production, the process is daemonized and I've specified a pidfile location. The rest of the settings are default.

Set your app running

Assuming you have the puma gem installed correctly you should be able to run your application complete with working ActionCable.

cd /home/myapp
puma -C config/puma.rb -e production

You should see the message “connected” pop up in your browser when you first load the page. Hopefully you’ve found this helpful but please let me know if I’ve missed anything.

 

Nested Attributes Not Setting Child IDs

Developing in Rails 5 recently I was struggling to think of why my child record IDs were not being set automatically on create. I thought this happened automatically. Then I discovered that setting inverse_of in the model is vital for accessing parent models from the child without relying on a pre-existing physical record (e.g. when you are creating new records). It turns out it’s been like this since Rails 2. How blind was I!

Here’s how it works:

class Publisher < ApplicationRecord
  has_many :articles, index_errors: true, dependent: :destroy, inverse_of: :publisher
  accepts_nested_attributes_for :articles, allow_destroy: true

class Article < ApplicationRecord
  belongs_to :publisher, inverse_of: :articles
  has_many :comments, index_errors: true, dependent: :destroy, inverse_of: :article
  accepts_nested_attributes_for :comments, allow_destroy: true, reject_if: proc { |item| item[:message].blank? }

class Comment < ApplicationRecord
  belongs_to :article, inverse_of: :comments

So using inverse_of allows me to access self.article from the Comment model even when the physical record has yet to be created.

Automated Rsync over SSH

The Mission

Let’s say we want to set-up rsync over SSH to securely backup a folder from one server to another. Our aim is to run a daily backup in cron with a command like this:

rsync -avz -e ssh /home/somefolder/important_files remoteuser@target_host:/home/mybackup/important_files_copy

On the Target Machine

The following steps work well for me but change as you wish (you may wish to create a folder under an existing user for example):

  1. Create a folder for the backup user:
    cd /home
    mkdir mybackupuser
  2. Next create the user, update the folder permissions and set the user password (make a note of this password):
    useradd -d /home/mybackupuser mybackupuser
    chown mybackupuser.root mybackupuser
    chmod 750 mybackupuser
    passwd mybackupuser
  3. Update the sshd_config file to allow this user to be used for ssh logins:
    vi /etc/ssh/sshd_config

    Add or amend the “AllowUsers” directive:

    AllowUsers mybackupuser
  4. Create the hidden folder for authorized keys:
    cd /home/mybackupuser
    mkdir .ssh
    chown mybackupuser.root .ssh
    chmod 700 .ssh
  5. Create the security script (attempt to filter any ssh command except rsync –server):
    cd /home/mybackupuser
    vi valid-rsync

    Copy and paste the following lines and then save the file:

    #!/bin/sh
    
    case "$SSH_ORIGINAL_COMMAND" in
    *\&*)
    echo "Rejected"
    ;;
    *\(*)
    echo "Rejected"
    ;;
    *\{*)
    echo "Rejected"
    ;;
    *\;*)
    echo "Rejected"
    ;;
    *\<*)
    echo "Rejected"
    ;;
    *\`*)
    echo "Rejected"
    ;;
    *\|*)
    echo "Rejected"
    ;;
    rsync\ --server*)
    $SSH_ORIGINAL_COMMAND
    ;;
    *)
    echo "Rejected"
    ;;
    esac

    I cannot claim credit for writing the above script but unfortunately I can’t remember where I got it from originally. Suffice to say it seems to be quite widely distributed on the web. To whoever wrote it: thank you!

  6. Set privileges for this file:
    chown mybackupuser.root valid-rsync
    chmod 700 valid-rsync

On the Source Machine (mostly!)

Now, in case you were getting bored, we move to the source machine to create an ssh key. The public part of this key will then be copied to the target machine.

  1. Log in as the user that your backups will run under. For example, if your backup script will run under the root user (usual) log in as root (or sudo su).
  2. Generate an SSH key pair:
    ssh-keygen -t rsa -b 2048

    Or, if it’s not your own server then you might want to specify a path for the key generation (rather than the default which is ~/.ssh/id_rsa):

    ssh-keygen -t rsa -b 2048 -f /home/someuser/rsync-key
  3. Important: accept all defaults and just press [Enter] when asked for the passphrase
  4. Upload the file from the source machine to the target machine using secure copy:
    scp ~/.ssh/id_rsa.pub mybackupuser@target_host:.ssh/authorized_keys

    If you specified a path (the -f option) then use the following command:

    scp /home/someuser/rsync-key.pub mybackupuser@target_host:/home/mybackupuser

    Now append the contents of rsync-key.pub to /home/mybackupuser/.ssh/authorized_keys on the target machine.

  5. On the target machine, prefix the key with additional security measures including the ip address and valid-rsync command file that you created earlier:
    from="xx.xx.xx.xx",command="/home/jjbb/valid-rsync" ssh-rsa ASHAKJSDHhaisudhfaksjfhHAISUDHiauegfkjaHSDKJHDjkh.....

    “xx.xx.xx.xx” is the IP address of the source machine.

  6. Now you are ready to rsync:
    rsync -avz -e ssh /home/somefolder/important_files remoteuser@target_host:/home/mybackup/important_files_copy

    If you are using your own file (see -f option earlier) you must add “-i /home/someuser/rsync-key” to the ssh options thus:

    rsync -avz -e "ssh -i /home/someuser/rsync-key" /home/somefolder/important_files remoteuser@target_host:/home/mybackup/important_files_copy

Disable Private Browsing / Incognito Mode

Private browsing may be handy if you want to cover your tracks; naturally you are not looking at anything you shouldn’t but merely planning a nice surprise for a loved one and don’t want them to find out. However, if you are a parent seeking to protect their children or an employer who doesn’t want their employees wasting time then disabling private browsing is a helpful step in reducing temptation: if they know they can’t browse privately then they may be less inclined to look at stuff they shouldn’t.

Internet Explorer

IE requires a Windows Registry edit. Simply paste the required settings from the lines below into a text file (e.g. “ie.reg”) and then “run” it to merge these settings into the Windows Registry.

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Internet Explorer\Control Panel]
"DisableDeleteBrowsingHistory"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Internet Explorer\Privacy]
"EnableInPrivateBrowsing"=dword:00000000

[HKEY_CURRENT_USER\Software\Policies\Microsoft\Internet Explorer\Control Panel]
"DisableDeleteBrowsingHistory"=dword:00000001

[HKEY_CURRENT_USER\Software\Policies\Microsoft\Internet Explorer\Privacy]
"EnableInPrivateBrowsing"=dword:00000000

I believe this works on versions 8, 9 and 10. Please let me know if not. I have included settings for both the whole machine and current user, you don’t actually need both.

I have also included the “DisableDeleteBrowsingHistory” setting as this is useful.

Firefox

Firefox is the easiest. You simply have to install the “Disable Private Browsing Plus” add-on and then you can do what many marketers do like buying youtube views for example. Once added it cannot be easily removed once added. You can find it here:

https://addons.mozilla.org/en-US/firefox/addon/disable-private-browsing-pl/?src=api

Google Chrome

Chrome used to accept registry settings similar to IE but that is no longer the case. You now need to download and install Group Policy Templates from here:
http://dl.google.com/dl/edgedl/chrome/policy/policy_templates.zip

Then follow this procedure:

  1. Unzip the policy_templates.zip file.
  2. From the Windows Start button enter “gpedit.msc” in the search box and press [Enter]. This will open the Local Group Policy Editor.
  3. Either under Computer Configuration or User Configuration (as you wish), right-click on “Administrative Templates” and click “Add/Remove Templates”.
  4. Click “Add” and open the file “policy_templates\windows\adm\en-GB\chrome.adm” (replace the “en-GB” with your own country).
  5. Once installed, open up the “Administrative Templates” and you will see either a new folder “Google” or a new folder “Classic Administrative Templates (ADM)”. The latter on Windows 7/8.
  6. Open up “Google” and then click on “Google Chrome” and you will see a whole raft of settings (for more info: http://www.chromium.org/administrators/policy-list-3).
  7. Double-click “Incognito mode availability” and a dialog will open.
  8. Now the confusing bit: select “Enabled” and then in the drop-down list underneath select “Incognito mode disabled”.
  9. You can check which policies are active by going to “chrome://policy/” in Chrome.

For more information please see this article:
https://support.google.com/chrome/a/answer/187202?hl=en

Speeding up the Android Emulator (Intel)

If you have installed the Android SDK but find the emulator hopelessly slow (even on a fast PC) then this might just make your day. It did mine; in my adult nerdhood I am easily pleased.

Install the Intel x86 Emulator Accelerator (HAXM):

  1. Start the Android SDK Manager by right-clicking and seleting Run as administrator (you’ll only need to run as administrator if you installed the SDK in a restricted location such as /Program Files, like I did – doh!).
  2. Tick Intel x86 Emulator Accelerator (HAXM) under Extras and install.
  3. Make sure your chosen platform has Intel x86 Atom System Image installed.

Check your configuration in the AVD Manager:

  1. Set your CPU/ABI to Intel Atom (x86).
  2. Tick Use Host GPU for faster graphics (display problems can occur so I have un-ticked this for now).

If you try running the emulator now you will get the following error:

emulator: Failed to open the HAX device!

emulator: Open HAX device failed

HAX is not working and emulator runs in emulation mode

To fix this you simply need to run the installation program for HAXM:

  1. Goto ..android-sdk/extras/intel/Hardware_Accelerated_Execution_Manager.
  2. Run the program haxm-windows_r02.exe.

You should now find that your Android Emulator runs a lot faster!
See you soon…on www.workbootsnerd.com

Windows 7 + Acrobat + Xerox – half-page reduction problem

Does your Adobe Acrobat/Reader always print on your Xerox Phaser 8550 (or other Xerox) with half-size output locked into landscape even when you force orientation to portrait? An illustration will explain the problem better:

half-page xerox/adobe reduction problem

It appears that this is a legacy driver problem. Here is the fix I used:

  1. Goto Devices and Printers → remove the offending Xerox printer!
  2. Goto the Xerox website → download the Xerox Global Print Driver (dated 9-Nov-2012 at the time of writing). Make sure it is WHQL certified.
  3. Select Install from Web.
  4. Delete the old drivers → highlight another printer → click Printer server properties at the top of the Control Panel window
  5. Click Drivers → highlight your Xerox driver(s) → click Remove → select Remove driver and driver package.
  6. Reboot.
  7. Run the set-up program (in my case Setup.5.273.23.2.exe).
  8. The set-up program should detect your printer which you should then select and click Next.
  9. Select your required option(s) and click Install (I chose Postscript).

Thank you to those who took the trouble to post their solution in the Adobe Community.