Saturday, September 5, 2020

More safe browsing with RaspberryPi and PiHole

To restrict access to unwanted websites:

 Go To PiHole Settings -> DNS->Upstream DNS Servers

I use the upstream DNS servers provided by

Enter the IPs provided by CleanBrowsing into the IPv6 and IPv4 input fields

Scroll down to Advanced DNS settings and enable these

  • Never forward non-FQDNs
  • Never forward reverse lookups for private IP ranges (only if you use PiHole as your DHCP server instead of your router)
  • Use DNSSEC

Safe Search with Raspberry Pi and PiHole

 Instructions adapted from this thread:

Add these to /etc/hosts

create this file  /etc/dnsmasq.d/05-restrict.conf with these lines

# YouTube,,,,,

# SafeSearch,

# Bing Family Filter,

# DuckDuckGo Filter,,

Finally restart PiHole. Tested with PiHole 5.1.2

Saturday, September 8, 2018

How to setup tiny tiny rss on a raspberry pi

I normally use Feedly (the app on iOS and the website) to collect all my RSS feeds. After upgrading my PiHole DNS server's Raspberry Pi from the 3B to 3B+ I had a spare pi on hand. I decided to try using it as an Tiny Tiny RSS server. What follows are the steps needed to do this. It takes about 2 hours if all goes well :)

Note: I decided to use Postgres as the database for the Tiny Tiny RSS (TT-RSS) server since the TT-RSS documentation recommends it.

Warning: This guide does not have steps to secure your apache and postgres servers running on the pi. Do not expose this server on the internet. I used a VPN to connect to my local home network from the outside while travelling to read my feeds.

With that warning out of the way, here goes:
  • Enable SSH on the pi (instructions here - follow the section - Enable SSH on a headless Raspberry Pi)
  • After the Pi is setup, it has to be updated first
sudo apt update && sudo apt upgrade -y && sudo apt dist-upgrade
sudo apt-get autoremove
sudo apt-get clean
sudo apt-get check
  • I love vim for editing configuration files. So let's install that next
sudo apt-get install vim
  • Next install Apache
sudo apt-get install apache2
  • Then PHP and PHP libraries
sudo apt-get install php libapache2-mod-php
sudo apt-get install php7.0-mbstring
sudo apt-get install php7.0-xml
sudo apt-get install php-curl php-pgsql php-intl
  • Then install postgres. At the time of writing, the version available on the Raspbian repo is 9.6. You can find the version using
sudo apt-cache search postgresql | less
  • Install Postgres
sudo apt-get install postgresql-9.6
  • Install Git
sudo apt install git
  • go to the apache document root. This is where the TT-RSS server PHP code will reside
cd /var/www/
sudo chmod -R 777 html
cd html
  • now lets get the TT-RSS software
git clone tt-rss
  • time to prep Postgres. Login to Postgres and create the user and database which TT-RSS will use
sudo -u postgres psql postgres
  • at the prompt do the following
CREATE ROLE ttrssuser LOGIN PASSWORD 'put in a password here';
CREATE DATABASE ttrssdb WITH OWNER = ttrssuser;
  • type \q to exit the pgsql prompt
  • check if it worked
psql -h localhost -d ttrssdb -U ttrssuser -p 5432
  • enter the password you setup for the user. type \q to exit the pgsql prompt
  • The TT-RSS documentation recommends enabling PHP's opcode cache. Lets do that next
  • Where is the opcode library located? We need that so that we can enter it in the php.ini file
sudo find / -name
  • where is the php.ini to enable opcode cache in?
sudo find / -name php.ini
  • first backup the file
sudo cp -pv /etc/php/7.0/apache2/php.ini /etc/php/7.0/apache2/php.ini_BAK
  • edit the file
sudo vi /etc/php/7.0/apache2/php.ini
  • Add the following lines and save the file
in the ; Dynamic Extensions ; sectionadd the following lines
in the [opcache] section
  • check if the opcode cache is enabled and the PHP data objects driver for Postgres is loaded
  • to do this, create a info.php file in /var/www/html
sudo vi /var/www/html/info.php
  • put this line in the file and save it
<?php phpinfo(); ?>
  • Change owner to pi for this file
sudo chown pi:pi info.php
chmod 755 info.php
  • Restart apache
sudo service apache2 restart
  • From a browser hit the apache server
  • check for the following lines in the output
Opcode Caching     Up and Running
PDO drivers     pgsql
PostgreSQL(libpq) Version     9.6.7
  • now it's time to configure tt-rss. Back to the command line
  • Give write permissions to the pi user first
cd /var/www/html
chmod 777 tt-rss
cd tt-rss
chmod -R 777 cache/images
chmod -R 777 cache/upload
chmod -R 777 cache/export
chmod -R 777 cache/js
chmod -R 777 feed-icons
chmod -R 777 lock
  • From a browser hit the tiny tiny rss install link
  • enter your database connection details (for the hostname I used localhost), test the configuration and initialize the database
  • login to tt-rss (http://yourraspberrypi/tt-rss) and change the default password. Import your opml file. For this do the steps from this excellent Lifehacker article Step Four: Log In and Import Your Feeds
  • Enable api access so that you can use iOS apps to connect to the TT-RSS server. Click the Actions menu at the top-right corner of the screen and select Preferences. Next click the checkmark next to Enable API access. Save configuration by clicking the button at the bottom of the screen
  • setup crontab to refresh your feeds. To reduce wear on the Raspberry Pi SD card, I setup cron to run every 2 hours to refresh the feed. Back on the command line
crontab -e
  • add this line and save
1 */2 * * * /usr/bin/php /var/www/html/tt-rss/update.php --feeds --quiet

that's it. Enjoy your new RSS feed aggregator server!

For iOS I used the app Fiery Feeds to connect to my TT-RSS server over VPN to read my feeds.

Wednesday, July 4, 2018

Late night questions: Will duplicate rules be enforced even if the data load happens via the data loader?

While playing with Matching and Duplicate rules in Salesforce, I got a question on what the behavior would be if a record matching existing data was uploaded through the dataloader. The duplicate matching rules should kick-in but I had to try it out to see if for myself.

As with most doubts in Salesforce, this one could be easily tested using a developer sandbox.

Test Setup:

  1. Create and activate a contact matching rule to match contacts exactly on email and fuzzily (is that a valid word?) on the first name
  2. Create and activate a Duplicate matching rule to block creation of the duplicates. 
  3. Create a sample contact record using the default SFDC UI
  4. Download the record through SFDC Dataloader to serve as a CSV upload template
  5. Edit the downloaded file and remove the Id column. Change the first name to be close the initial value (say Rose to Rosey)
  6. Try to upload this file through the data loader

Data loader outputs an error to the error log:


"Test","Rosey","123 Main St","","Use one of these records?"

Use one of these records?
This is the default message configured on the Duplicate Matching rule. This clearly shows Duplicate rules are being enforced even when the dataloader is used to insert data

Interestingly, this behavior was the same even if the duplicate rule was set to Action On Create = Allow - you still get an error and the Contact is not created. This is different from what the SFDC UI would do - it would allow contact creation.

Tested on Summer '18 Patch 11.4

Wednesday, January 11, 2017

Solving TLS error with SoapUI 5.3 and Salesforce

I faced an issue using the plain vanilla version of SoapUI 5.3 to connect to Salesforce while using the login method of the enterprise WSDL

Salesforce returns an error : UNSUPPORTED_CLIENT

Reading the error, it says that TLS1.0 has been disabled.

SoapUI 5.3 TLS error

You can fix this error with these steps

  1. go to the location where SOAP UI is installed (${HOME}/SoapUI/bin)
  2. edit the SoapUI-5.3.0.vmoptions file
  3. add this line to the end -Dsoapui.https.protocols=TLSv1.2
  4. save and close SoapUI-5.3.0.vmoptions
  5. close and reopen SoapUI. Try the login request again

Thursday, August 4, 2016

Inserting records with inactive owners or inactive user lookups

Recently I had to upload records into Salesforce from a legacy system. The catch here was that the records were for an managed package object and the records were owned by people who were no longer with the company. The records were being brought in for historical completeness of the Salesforce system.

There were a few interesting questions which were raised by this
  1. Can we upload a record owned by an inactive user?
  2. Can we upload records which reference inactive users in lookup fields?
  3. since we are inserting managed package object records,
    • should the record owner need a managed package license?
    • should the users referenced in the lookup fields need a managed package license?

Note: this is different from the newly released Winter 16 feature - "Set Audit Fields and Update Records with Inactive Owners []". That talks about Updating records owned by inactive users not inserting them.

I created a sample data load CSV and tried these out on my developer sandbox. 

I found -

  • Records cannot be inserted with inactive users as Owners. I got the following error from Salesforce when I tried to insert a record in sandbox - Operation Performed With Inactive User <15 char user ID> As Owner Of <15 char record ID>
    • It was decided that we would insert the records using a dummy Migration User owner record.
  • Records can have inactive users referenced in lookups. I tried to insert a record with active Owner, but with an inactive user referenced in a field on the record. This worked ok.
  • Inactive users do not need a managed package license to be in lookups. For the next trial, about whether inactive users require a managed package license to be in a field reference, this is the scenario: There is an inactive user A. There is a managed package object B with a text field text__c and a lookup field to user, say operator__c. Can we load a record like this one into object B though A is inactive and doesn't have the managed package license ?
'Test',15 character id of A.
The answer is yes. You can have inactive users in lookup references. Also the inactive user will not need a managed package license.
  •  Inactive users can own records of managed package objects without requiring a managed package license. Since we cannot load records with inactive user owners, the question of having a managed package license doesn't matter for data load. But I decided to try it out in a different scenario: say a record was created by an active user having the managed package license. Later the user was made inactive since the user left the org. Should the user record still have a managed package license or can we revoke it?
Brief background here: Inactive users in a Salesforce org assigned to a package license will be counted as an active user of a managed package in the licenses section. This article [] talks about it.

Obviously we don't want managed package licenses tied to inactive users. We want them to be freed up for active actual users.

I found that inactive users can own records of managed package objects. They don't need to have the managed package license. Salesforce support confirmed this.

Hopefully this helps someone!


Wednesday, May 25, 2016

Search Salesforce Setup quickly

Here's a note from the Salesforce documentation on a shortcut link to search in the setup for approval
items, assignment rules, custom objects and fields, custom profiles, permission sets, workflow items, users, groups, queues, email alerts and templates.


  • this is different from the global search at the top of every Salesforce screen which only searches records not setup configuration items
  • this only works if Advanced Setup Search is enabled.

https://<instance><your search term here>

Examples are:

Or for multiple keywords put a %20 in place of a space

Also, for developers there is a Chrome extension - Salesforce advanced Code searcher[linked in the references] which enables searching for apex and VF code. This extension is awesome in finding the apex or VF you need and quickly jumping to it.

Searching Setup with Advanced Setup Search []
Salesforce advanced Code searcher[]