IT and beyond

3-2-1 backup is not a timer to backup. It’s a well covered backup strategy to avoid data loss storing 3 copies of valuable data in different places. At least 3 backups, 2 of which are locally stored in different devices, and 1 is stored externally, for example a cloud storage service.

Here’s a recipe of a variant of a 3-2-1 backup in a real case scenario.


  • A local linux box (local from now on) with SSD
  • An external HDD
  • A remote system to backup (from now on remote)


  • Jenkins on local
  • Rsync on remote
  • (optional) Graphical interface on local for quick VPN setup


1 hour

The real world scenario involves a remote system on a VPN. First, use public-private keys pair to configure the connection. Since it’s a local linux box, user interface connection is very quick and easy to configure (1). Once it is set up, you can check if connection is active or activate the connection to VPN programmatically (2) as in this groovy:

Find and replace:

  • your-vpn-connection-name: name you’ve choosen on step (1)
  • local: name of your Jenkins node, with already configured agent

node ("local") {
   stage("vpn up") {
       // Disconnect if already connected
       sh '''
       IS_ACTIVE=`nmcli con show --active | grep your-vpn-connection-name | wc -l`
       if [ $IS_ACTIVE -eq "1" ]
            echo 'already connected, continue';
            # Connect to VPN set up with
            sudo nmcli con up id your-vpn-connection-name

To use nmcli, jenkins user on local must have access to the nmcli command. Grant these permissions (3) using:

sudo visudo

add those lines to sudoers:

Find and replace:

  • jenkinsuser: name of unix user on local

# on Cmnd alias specification
Cmnd_Alias      VPN_CONNECT = /usr/bin/nmcli
# on bottom
# jenkinsuser can activate / deactivate network interfaces using nmcli and use rsync
jenkinsuser ALL=(ALL) NOPASSWD: /usr/bin/rsync, VPN_CONNECT

When adding or removing something from visudo, or even when you add the jenkinsuser to some groups, restart the Jenkins agent (4) via interface to force a logout-login.

Running pipeline (2), now local should have access to remote VPN.

Download backup locally

Suppose you’ve already a couple of directories under /var/backup periodically synced using a custom cron script or something like rsnapshot. This backup is the first copy of a 3-2-1. backup set. Now, 3-2-1 approach will make you duplicate the copy on a different device on the same machine. The following commands, run on your local linux box, will pull the copy from remote to local. This is the last 1 copy of 3-2-1 backup.

To create the copy locally, you have to download from remote to local using rsync (5). Here’s a very simple command:

Find and replace:

  • user: remote user (optional if specified in ~/.ssh/config)
  • remotehost: remote host (via ~/.ssh/config)
export BKDIR=/opt/backup/remotehost
rsync -rltvz --no-o --no-g user@remotehost:/var/backup/db .
rsync -rltvz --no-o --no-g user@remotehost:/var/backup/images .

Command explained via explainshell

A more classical approach is to use rsync -av to preserve all permissions, but since resources are plain (compressed) SQL files or images there’s no reason to keep user, group or permission. Change the above command as you like it for your use case.

4-1-2-1 variant

Starting this article I’ve cited I used a variant of a 3-2-1 approach: call it 4-1-2-1 since it doesn’t formally exist.

There are 4 total copies:

  • 1 on remote
  • 2 on local, on different devices (1 SSD, 1 HDD)
  • 1 on a cloud storage service (backblaze)

A reliable approach to create a second copy on local is to redo the same commands on step (5) on a different $BCKDIR where the external HDD on local is mounted. However, if you want to unify all backups in a single pipeline to periodically update the whole backup you can do something like this:

node ("local") {
   echo "Copy to external HDD"
   stage("redundant backup") {
       sh "rsync -rltvzO /opt/backup/ /mnt/myexternalhdd/backup/secondcopy/"

Now you’ve all valuable data locally, you can use some external service as backup of last resort. I’ll cover this topic soon on part 2.

On github you can use a deploy key at a time for user associating to a private key configured in .ssh/config file.

  1. Create a new user on system (e.g. userperrepo)
  2. Generate a public / private pair following github howto (e.g. /home/userperrepo/.ssh/id_ecdsa_github)
  3. Associate the key with the repo on github
  4. Add or change config file on the system to associate with this private key

For example create a file named /home/userperrepo/.ssh/id_ecdsa_github with this content:

  User git
  IdentityFile /home/userperrepo/.ssh/id_ecdsa_github

Since for a github constraint a single deploy key cannot be used more than one time, you have to create a different user per repo to use git pull / push on private repo via ssh without any other configuration or command.

For any user created for deploy, specify git essentials:

git config --global "yourgithubname"
git config --global ""

In theory, you can chain multiple Host sections with different keys and the first working will be used. Practically, this is slow and error-prone so it’s better to use a different user per repo or try a different method.

Then you can pull and push from a private repo with a deploy key without any other commands.

If you want a single user to use multiple keys, you can follow this howto. But keep in mind that you can use this method using a docker container or a similar technology for a smoother deploy.

Windows 10

Can still be feasible to upgrade an existing Window 7, 32 bit installation on 2020? I tried and yes, it is.

Actually this is a suggestion of some of the best articles on this topic, this ZDnet howto by Ed Bott (archive) and this howtogeek howto by Chris Hoffman (archive) to convert a Windows 10 32bit to 64bit.

Tested upgrade path

I’ve walked a long installation path, starting from an 11 years old DVD. This is not the ideal scenario, but I got a very clean state to start from.

  1. Install Windows 7 Professional OEM from DVD or restore from system partition to get a clean state
  2. Upgrade Windows 7 Professional using Windows Update
  3. Upgrade to Windows 10 Pro 32bit, preserving the reserved partitions using the Upgrade this PC now path
  4. From Windows 10 32 bit, create an installation USB with Windows 10 Media creation tool choosing to create a media for an Other PC
  5. Install the new system above the old, preserving the reserved partitions


The howtos cited explains the above steps, but here are some supplmental tips on different scenarios, plus some things that maybe aren’t so clear at start. I’ve tested all of these:

  • You need an installed Windows 7 version to do the update following the howtos above.
  • Do not mess with reserved partitions: I don’t know if it will broke the upgrade path, but I don’t and it works.
  • You can use Clonezilla to make copies of your OS any time. Personally I do:
    • Before the Windows 7 to Windows 10 update (between 2 and 3)
    • After the 32bit installation (before 4)
    • After the 64bit installation (after 5)
  • Using Clonezilla, you can easily change the Hard Drive keeping the previous state of the OS intact if something goes wrong, ready to be restored.
  • If you have a Windows 7 Professional OEM, you will get a Windows 10 Pro with auto-activated digital license.
  • If you have Windows 7 on an unreadable DVD, try to change the reader before trying to clean up the surface.
  • If you have a DVD, make a copy with something like k3b and flash it on an USB with WoeUSB or similar to speed up the installation


By now Atlassian is dropping support to Mercurial on the popular Bitbucket service. Here is a proof of concept to use a Docker container as a separate environment where self-host your code using basic mercurial features without bells and whistles.

To do so, a docker container based on popular and lightweight jdeathe/centos-ssh image will be used. In this example, it’s supposed to use a remote server with docker service up and running.

1. Generate public / private pair

Create new keys to authenticate to the new container. Protect it with a password to deploy on external servers safely. In this example, an EdDSA type key is used.

ssh-keygen -t ed25519 -C "Key for xxx at xxx on xxx"

2. Choose keys and passwords

Choose a name for your new container here:


Create a new file named .env with the following content plus a custom:

  • content of the generated .pub file in the AUTHORIZED_KEYS row
  • a strong password to switch from hg to root via sudo su – root
  • timezone
SSH_AUTHORIZED_KEYS=*******PASTE PUB KEY here ***********
SSH_USER_PASSWORD=*******STRONG PASSWORD HERE (without ")***********
SYSTEM_TIMEZONE=********YOUR TIMEZONE HERE e.g. Europe/Rome***********

This configuration:

  • Allow the connection using the private key generated before
  • Disable password authentication
  • Set default user name to hg
  • Allow all users to switch to sudo (there will be only an hg user)
  • Set server with preferred timezone

3. Create the centos-ssh container

On the same directory where resides the .env file before, create a new container:

docker run -d \
  --name $SSHCONTAINER \
  -p 12120:22 \
  --env-file .env \
  -v /opt/path/to/some/host/dir:/home \
  • create a detached container named $SSHCONTAINER
  • expose the container on port 12120. If you want lo limit to localhost only, use or iptables will be set up to bind because docker mess up with iptables. You can also disable iptables on docker.
  • map the whole container /home directory to a new directory created by root on host /opt/path/to/some/host/dir:

Note: do not use ACL (e.g. setfacl) on /opt/path/to/some/host/dir or .ssh directory will broke (e.g. Bad owner or permissions)

4. Install mercurial on container

Now on container install mercurial and its dependencies. You can login as root using docker:

docker exec -it $SSHCONTAINER bash

or saving this script then chmod a+x it and launch:

set -e
docker exec -it -u root $SSHCONTAINER  yum install -y python36-devel python36-setuptools gcc
docker exec -it -u root $SSHCONTAINER /usr/bin/pip3 install mercurial

Restart the container:

docker container restart $SSHCONTAINER

Then check if mercurial is running for user hg:

docker exec -it -u hg $SSHCONTAINER hg --version

Then if container is running smoothly, you can update it to restart always on reboot or on docker service restart:

docker container update $SSHCONTAINER --restart always

then check if it’s applied:

docker container inspect $SSHCONTAINER | grep -B0 -A3 RestartPolicy

5. Login to container directly

Now on your local machine you can connect directly to the container using SSH without caring about the host.

By default an iptables rule is created by docker to allow connections from outside. Anyway, you have to specify the port and the user name .ssh/config like this:

    User hg
    Port 12120
    PreferredAuthentications publickey
    IdentityFile /home/chirale/.ssh/id_ed25519_mycodehosting_example_org

This configuration is useful when you create a subdomain exclusively to host code, then you associate it a port and a username to obtain a mercurial url like this:


where dir and subdir are directly in /home/hg directory of container, on host /opt/path/to/some/host/dir/hg/test/project. Differently from Bitbucket, you can have how many  directory level you want to host the project.

6. Create a test repo

Create a test repository inside this container. You can access everywhere with the above ssh configuration using:


Then you can

cd repo/
mkdir alba
cd alba/
hg init
hg verify
checking changesets
checking manifests
crosschecking files in changesets and manifests
checking files
checked 0 changesets with 0 changes to 0 files
cat > README.txt
hg addremove
HGUSER=your_username_here hg commit -m "First flight"

7. Clone the test repo

Then from everywhere you can clone the repo adding :

hg clone ssh://

You can commit and push now.

If you login to, no new file was added. You’ve simply to run

hg update

to get it. Note that you haven’t to update every time you push new commits on alba to Simply all changes are recorded, but not yet reflected on directory structure inside container.

If this is a problem for you, you can automate the update every time hg has a new changeset using for example supervisor service, shipped with centos-ssh.

Compare these:

parent: 1:9f51cd87d912 tip
 Second flight
branch: default
commit: (clean)
update: (current)
hg summary
missing pager command 'less', skipping pager
parent: 1:9f51cd87d912
 Second flight
branch: default
commit: (clean)
update: 1 new changesets (update)

The first hasn’t change update: (current), the second has update: 1 new changesets (update).

8. Migrate the code from Bitbucket to self-host

From the container, logged as hg user, import temporary your key to download the repository from old bitbucket location following Bitbucket docs, then:

cd ~
mkdir typeofproject
cd typeofproject
hg clone ssh://

Then you can alter the directory as you like:

  • edit the .hg/hgrc file changing parameters as you like
  • rename youroldbrepo directory

Remember to temporary store on the container the ssh keys and config to access to Bitbucket if any (permission should be 600). You can remove these keys when migration is done.

After a test clone you can drop the Bitbucket repo.

9. Find your flow

With a self-hosted solution you have to maintain the service. This is a relatively simple solution to set up and maintain.

If you are comfortable with old Bitbucket commit web display, you can use PyCharm to see a nice commit tree like this:

Tested on release 2.6.1 with centos:7.6.1810.

Talking about package manager on Linux, flatpak gained attention recently. Installation is easy, this is about installing on Ubuntu.

If you’ve some trouble installing an application using the package manager shipped with your distro, you can give it a try, since it’s available on 22 distro by now.

In the above case, torbrowser packet is broken on a Ubuntu 18.04, as many users noted out on the package page. Installing the package via flatpak, everything run smoothly in minutes.


flatpak search PACKAGENAME


flatpak install PACKAGENAME

Command line output is very complete, but if you want to use a GUI, on Ubuntu gnome-software (Ubuntu Software) will list the flatpak packages too, just read the package info on bottom of the description page and search packages as usual.

In this howto I will show how to backup all PostgreSQL databases, roles and permission on a server and restore them on another server on a docker.

1. Backup databases on origin server

To backup all PostgreSQL databases automatically, preserving roles and permission, you can create this cron.d script (this example is from a CentOS):

# Dump postgresql database automatically
40 22 * * 0 postgres /bin/bash -c 'pg_dumpall -Upostgres | gzip > "/var/backup/myexport.sql.gz"'

In order:

  1. Run on at 22:40 on Sunday
  2. Run as postgres user (cron is executed by root, and root can impersonate it without password)
  3. Use the bash interpreter (not sh)
  4. Run pg_dumpall to dump all database
  5. Pipe the output to gzip to compress the SQL into a /var/backup/myexport.sql.gz file

2. Transfer on another server

Transfer the dump using ssh using something like this:

rsync -rltvz --no-o --no-g myuser@originserver:/var/backup/dumpall.sql.gz /opt/backup/myproj/data/

Use .ssh/config to store connection info.

3. Restore on a clean docker container

The following bash script will create a docker container, populating it with

set -e
echo "Remove identical container, keep data on directory"
NOW=`date +%Y-%m-%d`
# create an id based on timestamp plus MAC address 
UNIQUID=`uuidgen -t`
echo "Get postgres docker image"
docker pull postgres:9.4-alpine
echo "Generate 1 password with 48 char length"
PPASS=`pwgen 48 1`
mkdir -p /opt/docker_data/$NAME
echo "Save psql password on /opt/docker_data/$NAME.pwd"
echo $PPASS > "/opt/docker_data/$NAME/psql.pwd"
echo "extract database"
mkdir -p /opt/docker_data/$NAME/psqldata
mkdir -p /opt/docker_data/$NAME/share
gunzip -c /opt/backup/myproj/data/dumpall.sql.gz > /opt/docker_data/$NAME/share/restore.out
echo "Run a clean docker"
docker run --name $NAME -e POSTGRES_PASSWORD=$PPASS -d -p 44432:5432 -v /opt/docker_data/$NAME/psqldata:/var/lib/postgresql/data -v /opt/docker_data/$NAME/share:/extshare --restart always postgres:9.4-alpine
sleep 10
echo "Restore from /extshare/restore.out using user postgres (-upostgres) the database postgres (all dbs)"
docker exec -upostgres $NAME psql -f /extshare/restore.out postgres
echo "Clear the restore.out file"
rm /opt/docker_data/$NAME/share/restore.out

In order, this script:

  1. Download a postgres:9.4-alpine image (choose your version)
  2. Generate a random container name postgresql-myproj-YYYY-MM-DD-random-id based on MAC address and timestamp
  3. Generate a random password and save on a file
  4. Generate a directory structure on host system to keep postgres file and dump outside docker
  5. Create a new postgres container exposed to host on port 44432
  6. Save postgres files on /opt/docker_data/$NAME/psqldata on host
  7. Expose the dump file directory on /extshare on guest
  8. Restore the dump using role postgres
  9. Delete the dump

Resulting directory structure on host will be:

└── postgresql-myproj-2019-28-12-*******
    ├── psqldata [error opening dir]
    ├── psql.pwd
    └── share
        └── restore.out

Then to connect just use the same user and password of the database on origin server.


Usually on /opt/docker_data/$NAME/psqldata/pg_hba.conf, you’ve to add a line like this:

host all all md5

giving to host (reachable by inside docker) full access to database. But the default image ship a handy, permissive entry:

host all all all md5

So you can connect without any step to the database.


If you connect to the destination server with ssh, if you cannot access the port remember to forward the PostgreSQL on .config like:

Host destinationsrv
    Hostname destinationaddr
    User mydestservuser
    IdentityFile /home/myuser/.ssh/id_rsa_destination_server
    LocalForward 44432

Remember that if you haven’t a firewall, docker container will start with a permissive rule like:>5432/tcp

So will be exposed and reachable using password.

The default configuration of Firefox can cause a search to be performed when an url is wrong using the main bar, or auto-suggestion to be performed silently. This setting may disclose to third parties local path when mistyped.

At the end of this howto, Firefox will be back to a status where the user take back more control of searches, reducing disclosed data to external services. You can also disable the suggestions when typing.

How to disable automatic search

To disable search on url bar of Firefox, first re-enable the double search bar:

  1. Go to Settings
  2. Go to Search settings
  3. On Search Bar, select the 2 bar option

Now disable the search on the url bar:

  1. Visit on the main bar about:config
  2. Accept to continue
  3. Search “keyword”
  4. Change the status from true to false (double click on it)

Now if you try to type a keyword, you will be redirected to the website, e.g. boh will redirect to

Data aren’t sent anymore to other search engines except for autocompletes, but you can disable it following next steps.

Disable search autocomplete on urlbar

  1. Go to about:config
  2. Search browser.urlbar.suggest.searches
  3. Set value from true to false (double click on it)

Now if you type a search on the url bar, the search will be performed on the history alone, but if you use the search bar autocomplete will works normally.

Disable search autocomplete on search bar

To completely disallow search suggestion on the new right searchbar:

  1. Look for on about:config
  2. Double click on it to set false

Disable auto .com

Note: if you wrongly type a search in the url bar, it will be autocompleted as (YOUR SEARCH + “.com”).

To disable the “.com” suffix or the “www” prefix:

  1. Look for browser.fixup.alternate.enabled on about:config
  2. Double click on it to set false

Final behaviour

With all these changes applied:

  1. The url bar will look for a URL exactly as you’ve typed (except for the https / http that will be autocompleted)
  2. The search bar will perform the search on the search engine you’ve specified
  3. No silent request will be forwarded to search engines on url bar or search bar (as far as I know)
Mount via systemd of a directory on RAM

This configuration will allow to install on a Debian-based system a fast server for client libraries. Key technologies used are:

  • tmpfs to serve files from volatile memory
  • git / mercurial from github / bitbucket to get files from a public or private repository
  • systemd units to mount tmpfs and sync
  • nginx to serve files to user

On this first step you’ll create a service to reserve some RAM for static files, pulling them from a private or public repo.

Mount tmpfs with systemd

To serve files directly from RAM, you have to mount a tmpfs directory. You can do it on fstab:


tmpfs /mnt/cdn tmpfs rw,nodev,nosuid,size=300M 0 0

Or with a systemd unit:


Description=Mount empty CDN directory on volatile memory


  • noatime will disable last access on contained files, reducing write on disk
  • size will reserve 300MB for /mnt/cdn partition on RAM (increase as needed)
  • mount the partition on runlevel 3 (multi-user mode with networking)

Create two units on a local path like /usr/local/share/systemd then create a symlinks on /etc/systemd/system or create directly them on /etc/systemd/system. You can also directly create them on /usr/local/share/systemd.

Create the pull service

When the /mnt/cdn is successfully loaded, pull static files from your repository.


Description=Pull on CDN directory.


  • Clone the git repository with a user on system using a key with an alias
  • Change youruserhere to the user who cloned the repository
  • Add to /root/.ssh/config and to  /root/.ssh/my_private_key the private key to do the pull


  • WantedBy=mnt-cdn.mount copy the files to RAM only after the /mnt/cdn is created
  • pull the repository only when the network is ready

On pull, all files will be written by root as youruserhere:youruserhere.

After the pull, to reduce RAM occupation, this script doesn’t download directly to RAM .git directory but copy them with rsync excluding them:


# stop on first error
set -e
cd /srv/cdn-all
git pull
exec rsync -a --exclude=.git --exclude=.gitignore /srv/cdn-all/* /mnt/cdn/

Get systemd to know about the mount and service

To reload systemd units, you have to

systemctl daemon-reload

Then do the mount via the systemd unit:

systemctl start mnt-cdn.mount

Enable on boot

Since the cdn-pull.service is tied to mnt-cdn.mount, both have to be enabled to run:

systemctl enable mnt-cdn.mount
systemctl enable cdn-pull.service
  1. When the system is ready create the tmpfs on /mnt/cdn/
  2. After tmpfs is successfully created by the unit, the file will be automatically synced through cdn-pull.service.

Mount will auto-start sync

Start only the mnt-cdn.mount:

systemctl start mnt-cdn.mount

And then ask for info about both services:

systemctl status mnt-cdn.mount
systemctl status cdn-pull.service
  • mnt-cdn.mount have to be active (mounted)
  • cdn-pull.service should be active (script is running) or inactive (sync is completed). In both cases, it’s ok.

With this set-up, when you restart the mnt-cdn.mount files will be automatically pulled and synced to RAM when system starts and when you start or restart mnt-cdn.mount service.

Next you can serve these files on nginx and the final step could be to auto-detect push to update files automagically.

See also

This howto will show how to restart automatically a nodejs app on crash or on file changes using supervisor and nodemon.

Autorestart on changes

To install nodemon to autorestart app when you change app files:

npm install -g nodemon

To test it’s working, use nodemon like node, passing all parameters you would pass to node:

nodemon app.js --myoptionalparameter MYVALUE;

Autorestart on errors

To install supervisor on a Debian-based system to restart app on crashes:

sudo apt-get install supervisor

Then create a wrapper script on a custom location to monitor:

cd /path/to/my/app;
exec node app.js --myoptionalparameter MYVALUE;
  • exec is very important since it gave to supervisor the control of the process
  • if you specify nodemon instead of node , the app will not autorestart on crashes but only on changes. On production, only node should be used while on development nodemon is fine to track errors.

Now set up the config file for supervisor creating a new file on /etc/supervisor/conf.d/myapp.conf with:

command=bash /path/to/my/
priority=10                ; the relative start priority (default 999)
autostart=true              ; start at supervisord start (default: true)
autorestart=true            ; retstart at unexpected quit (default: true)
; startsecs=-1                ; number of secs prog must stay running (def. 10)
; startretries=3              ; max # of serial start failures (default 3)
exitcodes=0,2               ; 'expected' exit codes for process (default 0,2)
stopsignal=QUIT             ; signal used to kill process (default TERM)
; stopwaitsecs=10             ; max num secs to wait before SIGKILL (default 10)
user=USER_TO_RUN_APP_HERE                   ; setuid to this UNIX account to run the program
log_stdout=true             ; if true, log program stdout (default true)
log_stderr=true             ; if true, log program stderr (def false)
logfile_maxbytes=10MB        ; max # logfile bytes b4 rotation (default 50MB)
logfile_backups=10          ; # of logfile backups (default 10)
  • change USER_TO_RUN_APP_HERE to a system user who can access to app files and directory

Now you have to reread to apply changes without restarting all other services:

sudo supervisorctl reread

So in case of errors you got something like:

ERROR: CANT_REREAD: Invalid user name fakeuserhere in section ‘program:myapp’ (file: ‘/etc/supervisor/conf.d/myapp.conf’)

On success:

myapp: available

If you’ve changed the app configuration, you have to:

sudo supervisorctl update

To apply, then restart the specific app.

sudo supervisorctl restart myapp

Keep an eye on supervisor processes with:

sudo supervisorctl status


myapp                            RUNNING   pid ****, uptime 0:00:59
anotherapp                       RUNNING   pid ****, uptime 0:29:33

Control the processes

Since exec was specified in the wrapper script before, supervisor can stop the node app on demand with:

sudo supervisorctl stop myapp

Then supervisorctl status will display something like this:

myapp                             STOPPED   Apr 27 22:53 AM
anotherapp                        RUNNING   pid ****, uptime 0:28:16

To run again:

sudo supervisorctl start myapp
  • When you will restart the service with systemctl restart supervisor, all /supervisor/conf.d/ files will be read again, then if they are set to autostart they will even if you’ve stopped them.
  • If your node.js app has more than one file to run (e.g. a couple of servers) you can copy and append the [program:myapp] configuration on the same file changing this second block to something like [program:myapptwo] specifying a new wrapper script

You can use multiple deploy keys for Github created with ssh-keygen following with these steps.

You have to add to your ~/.ssh/config

Host github_deploy_key_1
    User git
    IdentityFile ~/.ssh/github_deploy_key_1_rsa

Host github_deploy_key_2
    User git
    IdentityFile ~/.ssh/github_deploy_key_2_rsa

If you haven’t added your github name on git:

git config --global "yourgithubname"
git config --global ""

Then clone your repository specifying your custom host, adapting what github suggest to you on repo page:

git clone git@github_deploy_key_1:yourgithubname/your-repo.git

If you have enabled push permissions you can use this deploy key even to update the repository.

In this way you can keep a server clean from your github passepartout and add only the keys it needs.

%d bloggers like this: