May 31, 2018

Docker-based HTPC set-up

This is a quick run through for setting up a HTPC (Home Theatre PC). It requires a docker installation, which is available for most modern Operating systems (official installation instructions). These instructions were completed with linux so it may not work 100% "as-is" for other operating systems. It will use the following components:


One thing to note is that I use a separate docker network to isolate all my services from the default bridge network. You can create your own using: docker network create traefik



No-IP for Dynamic DNS

If you have a simple home router with a dynamic IP but still want to access your content from the internet, you need to set-up a Dynamic DNS. There are paid services out there, but No-IP provides you with a free hostname (it does use ads, and hostnames expire every month). You will need to create an account with your VPN provider and then run the following:


docker run --name=noip -d \
  -v /etc/localtime:/etc/localtime:ro\
  -v [PATH_TO_CONFIG]:/config\
  coppit/no-ip


Note that [PATH_TO_CONFIG] must exist on your local hard-drive.

On your initial run, the container will create a file in the configuration directory. Open up that file, enter in your username, password and domain names. Then re-run your container and your dynamic DNS will be set-up!

NOTE: The rest of this guide will assume you have ports 443 and 80 forwarded on your modem to your Media PC. You may also need to poke a hole in any firewall you have for these ports. Without this completed then Traefik will not work.


Traefik for Reverse Proxy

Traefik will be used to take all traffic from your No-IP hostname and direct them to the desired services. Before you start:
  1. Create a folder to store your configuration data
  2. Create two empty files: traefik.toml and acme.json
  3. Make sure that acme.json has only 600 privileges (user read/write only, no execute, no permissions for group or other). I am unsure how to do this for Windows.
  4. Use htpassword or similar tool to encode a basic password auth string. In Linux, you would download it using sudo apt-get install apache2-utils and then run htpasswd -nb admin secure_password
  5. Put the following into traefik.toml (fill in your own variables for {!!EMAIL!!}, {!!DOMAIN!!}, and {!!HTPASSWORD_OUTPUT!!}):
################################################################
# Web configuration backend
################################################################
defaultEntryPoints = ["http","https"]
[entryPoints]
[entryPoints.http]
address = ":80"
#[entryPoints.http.redirect]
#entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]

[web]
address = ":8080"
[web.auth.basic]
users = ["admin:$apr1$62BTL9Hv$DvZO5S4l6FClkYbuxn4ag/"]

[accessLog]

################################################################
# Docker configuration backend
################################################################
[docker]
domain = "zeitoune.hopto.org"
watch = true
exposedbydefault = false

[acme]
email = "nassar.z@slair.com.au"
storage = "acme.json"
entryPoint = "https"
OnHostRule = true



Then run the following command to create your docker container:


docker run -d --name=traefik --restart always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v [PATH_TO_TRAEFIK]/traefik.toml:/traefik.toml \
  -v [PATH_TO_TRAEFIK]/acme.json:/acme.json \
  -l 'traefik.backend=traefik' \
  -l 'traefik.frontend.rule=PathPrefixStrip:/traefik' \
  -l 'traefik.enable=true' \
  -l 'traefik.docker.network=[DOCKER_NETWORK]' \
  -l 'traefik.port=8080' \
  --net=traefik \
  -p 80:80 -p 443:443 \
  --expose 8080 \
  traefik:latest


Note that [PATH_TO_TRAEFIK] must exist on your local hard-drive, and [DOCKER_NETWORK] is the network created using the command given at the start of this guide.

Once completed, you should be able to view traefik at https://[HOSTNAME]/traefik


Portainer for Docker Management

Portainer is a useful GUI tool for managing your docker containers (official installation instructions). Just run the following:

docker run -d --restart always \
  --name=portainer -p 9000:9000 --net=traefik \
  -v /etc/localtime:/etc/localtime:ro \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v [PATH_TO_DATA]:/data \
  -l 'traefik.backend=portainer' \
  -l 'traefik.frontend.rule=PathPrefixStrip:/portainer' \
  -l 'traefik.enable=true' \
  -l 'traefik.docker.network=[DOCKER_NETWORK]' \
  -l 'traefik.port=9000' \
portainer/portainer

Note that [PATH_TO_DATA] must exist on your local hard-drive. This is where all the Portainer configuration data will be stored, and is necessary in case you ever wish to update the container.

The portainer instance can now be viewed at https://[HOSTNAME]/portainer/ OR http://[IP_ADDRESS]:9000


Heimdall for Dashboard

Heimdall can be configured as a one-stop shop for accessing all of your configured microservices.


docker run -d --restart always \
  --name=heimdall \
  -v [PATH_TO_DATA]:/config \
  -e PGID=1000 \
  -e PUID=1000 \
  --net=traefik \
  -l 'traefik.backend=heimdall' \
  -l 'traefik.enable=true' \
  -l 'traefik.docker.network=traefik' \
  -l 'traefik.frontend.rule=Host:zeitoune.hopto.org' \
  -l 'traefik.frontend.priority=1'
  -l 'traefik.port=80' \
  linuxserver/heimdall

Note that [PATH_TO_DATA] must exist on your local hard-drive. This is where all the Heimdall configuration data will be stored, and is necessary in case you ever wish to update the container.

The heimdall instance can now be viewed at https://[HOSTNAME]/


Calibre for E-Books

Calibre is a management service for E-Books.

docker run -d --restart always \
  --name=calibre \
  -v [PATH_TO_BOOKS]:/books \
  -v [PATH_TO_APP]:/calibre-web/app \
  -v [PATH_TO_CONFIG]:/calibre-web/config \
  -v [PATH_TO_KINDLEGEN]:/calibre-web/kindlegen \
  -v /etc/localtime:/etc/localtime:ro \
  -e PGID=1000 \
  -e PUID=1000 \
  --net=traefik \
  -l 'traefik.backend=calibre' \
  -l 'traefik.enable=true' \
  -l 'traefik.docker.network=traefik' \
  -l 'traefik.frontend.rule=PathPrefixStrip:/calibre' \
  -l 'traefik.frontend.headers.customRequestHeaders=X-Script-Name:/calibre' \
  -l 'traefik.port=8083' \
  technosoft2000/calibre-web


Note that [PATH_TO_*] must exist on your local hard-drive. This is where all the Calibre data will be stored, and is necessary in case you ever wish to update the container.

The calibre instance can now be viewed at https://[HOSTNAME]/calibre


Ombi for requests

Use Ombi for making media requests.

docker run -d --restart always \
  --name=ombi \
  -v [PATH_TO_CONFIG]:/config \
  -e PGID=1000 \
  -e PUID=1000 \
  -e TZ=Australia/Sydney \
  --net=traefik \
  -l 'traefik.backend=ombi' \
  -l 'traefik.enable=true' \
  -l 'traefik.docker.network=traefik' \
  -l 'traefik.frontend.rule=PathPrefix:/ombi' \
  -l 'traefik.port=3579' \
  -p 3579:3579 \
  linuxserver/ombi

Note that [PATH_TO_CONFIG] must exist on your local hard-drive. This is where all the Ombi data will be stored, and is necessary in case you ever wish to update the container.

Note also that in this case I could only get Ombi to work by opening port 3576, navigating to the URL directly and editing the configuration so that the Base URL is '/ombi' instead of '/'. Once you have that configuration, you can close port 3579 again.

The ombi instance can now be viewed at https://[HOSTNAME]/ombi


Emby for Media

docker run -d --restart always \
  --name=emby \
  -v [PATH_TO_CONFIG]:/config \
  -v /etc/localtime:/etc/localtime:ro \
  -v [PATH_TO_MEDIA]:/media
  -e GID=1000 \
  -e UID=1000 \
  --net=traefik \
  -l 'traefik.backend=emby' \
  -l 'traefik.enable=true' \
  -l 'traefik.docker.network=traefik' \
  -l 'traefik.frontend.rule=PathPrefixStrip:/emby' \
  -l 'traefik.port=8096' \
  emby/embyserver

Note that [PATH_TO_*] must exist on your local hard-drive. This is where all the Emby data (and Media) will be stored, and is necessary in case you ever wish to update the container.

The Emby instance can now be viewed at https://[HOSTNAME]/emby/


Sonarr for TV shows

docker run -d --restart always \
  --name=sonarr \
  -v [PATH_TO_CONFIG]:/config \
  -v [PATH_TO_TV]:/tv \
  -v [PATH_TO_DOWNLOADS]:/downloads \
  -v /etc/localtime:/etc/localtime:ro \
  -e PGID=1000 \
  -e PUID=1000 \
  -e TZ=Australia/Sydney \
  --net=traefik \
  -l 'traefik.backend=sonarr' \
  -l 'traefik.enable=true' \
  -l 'traefik.docker.network=traefik' \
  -l 'traefik.frontend.headers.customRequestHeaders=X-Script-Name:/sonarr/' \
  -l 'traefik.frontend.rule=PathPrefix:/sonarr/' \
  -l 'traefik.port=8989' \
  --dns 8.8.8.8 --dns 8.8.4.4 \
linuxserver/sonarr

Note that [PATH_TO_*] must exist on your local hard-drive. This is where all the Sonarr data (and Media) will be stored, and is necessary in case you ever wish to update the container.

Note also that in this case I could only get Sonarr to work by opening port 8989, navigating to the URL directly and editing the configuration so that the Base URL is '/sonarr/' instead of '/'. Once you have that configuration, you can close port 3579 again.

The Sonarr instance can now be viewed at https://[HOSTNAME]/sonarr/


Radarr for Movies

docker run -d --restart always \
  --name=radarr \
  -v [PATH_TO_CONFIG]:/config \
  -v [PATH_TO_MOVIES]:/movies \
  -v [PATH_TO_DOWNLOADS]:/downloads \
  -v /etc/localtime:/etc/localtime:ro \
  -e PGID=1000 \
  -e PUID=1000 \
  -e TZ=Australia/Sydney \
  --net=traefik \
  -l 'traefik.backend=radarr' \
  -l 'traefik.enable=true' \
  -l 'traefik.docker.network=traefik' \
  -l 'traefik.frontend.headers.customRequestHeaders=X-Script-Name:/radarr/' \
  -l 'traefik.frontend.rule=PathPrefix:/radarr/' \
  -l 'traefik.port=7878' \
  --dns 8.8.8.8 --dns 8.8.4.4 \
  linuxserver/radarr

Note that [PATH_TO_*] must exist on your local hard-drive. This is where all the Radarr data (and Media) will be stored, and is necessary in case you ever wish to update the container.

Note also that in this case I could only get Radarr to work by opening port 7878, navigating to the URL directly and editing the configuration so that the Base URL is '/radarr/' instead of '/'. Once you have that configuration, you can close port 3579 again.

The Radarr instance can now be viewed at https://[HOSTNAME]/radarr/


Jackett for Indexers

docker run -d --restart always \
  --name=jackett \
  -v [PATH_TO_CONFIG]:/config \
  -v [PATH_TO_DOWNLOAD]:/downloads \
  -v /etc/localtime:/etc/localtime:ro \
  -p 9117:9117 \
  -e PUID=1000 -e PGID=1000 \
  -e TZ=Australia/Sydney \
  --net=traefik \
  -l 'traefik.enable=true' \
  -l 'traefik.backend=jackett' \
  -l 'traefik.frontend.rule=PathPrefix:/jackett' \
  -l 'traefik.port=9117' \
  -l 'traefik.docker.network=traefik' \
  --dns 8.8.8.8 --dns 8.8.4.4 \
  linuxserver/jackett

Note that [PATH_TO_*] must exist on your local hard-drive. This is where all the data (and Media) will be stored, and is necessary in case you ever wish to update the container.


Tranmission for Torrents

This is being run with an inbuilt VPN:

docker run -d --restart always \
  --name=transmission \
  --cap-add=NET_ADMIN --device=/dev/net/tun \
  -v /[PATH_TO_CONFIG]:/config \
  -v /[PATH_TO_DOWNLOADS]:/data \
  -v /etc/localtime:/etc/localtime:ro \
  -e OPENVPN_PROVIDER=PIA \
  -e OPENVPN_CONFIG=Netherlands \
  -e OPENVPN_USERNAME=user \
  -e OPENVPN_PASSWORD=pass \
  -e WEBPROXY_ENABLED=false \
  -e LOCAL_NETWORK=192.168.0.0/16 \
  -e TRANSMISSION_RPC_AUTHENTICATION_REQUIRED=true \
  -e TRANSMISSION_RPC_PASSWORD=[PASSWORD] \
  -e TRANSMISSION_RPC_USERNAME=admin \
  -e TRANSMISSION_RPC_HOST_WHITELIST="127.0.0.1,192.168.0.*" \
  --log-driver json-file \
  --log-opt max-size=10m \
  --net=traefik \
  --dns 8.8.8.8 --dns 8.8.4.4 \
  -p 9091:9091 \
  -l 'traefik.backend=transmission' \
  -l 'traefik.enable=true' \
  -l 'traefik.docker.network=traefik' \
  -l 'traefik.frontend.rule=PathPrefix:/transmission' \
  -l 'traefik.port=9091' \
haugene/transmission-openvpn

Note that [PATH_TO_*] must exist on your local hard-drive. This is where all the data (and Media) will be stored, and is necessary in case you ever wish to update the container.


Inspirations

This post was inspired by the following:

Mar 2, 2018

Automatically make web apps use HTTPS with Let's Encrypt, Nginx, and Docker

  1. Make sure you have docker already installed.
  2. Install the Nginx proxy with docker-gen
    
    sudo docker run --name=Nginx -d \
    --restart=always \
    -p 80:80 -p 443:443 \
    -v /data/certs:/etc/nginx/certs:ro \
    -v /var/run/docker.sock:/tmp/docker.sock:ro \
    -v /data/Nginx/vhost.d:/etc/nginx/vhost.d \
    -v /data/Nginx/html:/usr/share/nginx/html \
    --label com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy \
    jwilder/nginx-proxy
    

  3. Since I run portainer, start it up with the VIRTUAL_HOST and VIRTUAL_PORT environment variables so that docker-gen can pick it up. You can do this with any app you desire.
    
    sudo docker run --name Portainer -d \
    --restart=always \
    -p 9000:9000 \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v portainer_data:/data \
    -e VIRTUAL_HOST=portainer.local.network \
    -e VIRTUAL_PORT=9000 \
    portainer/portainer
    

  4. Now to use the Let's encrypt container to make certificates for our docker containers:
    
    sudo docker run --name=Letsencrypt -d \
    --restart=always \
    -v /data/certs:/etc/nginx/certs:rw \
    -v /var/run/docker.sock:/var/run/docker.sock:ro \
    --volumes-from Nginx \
    jrcs/letsencrypt-nginx-proxy-companion
    

  5. To enable SSL for your site, set the environment variables VIRTUAL_PROTO=https, VIRTUAL_PORT=433 environment as well as the LETSENCRYPT_HOST and LETSENCRYPT_EMAIL variables so that docker-gen can pick it up. You can do this with any app you desire. You will also need to mount the certificates and keys within the SSL folder of the container for the container to use the LetsEncrypt keys.

Mar 1, 2018

Making Docker Daemon listen on network port during start-up

Took a bit of time to find the relevant documentation piece, so I thought I would outline it here for easy reference.

Option 1:

This should work for some systems, although distributions that use systemctl may have their docker.service entry overwrite this setting, so you will need to use option 2.
  1. If not already created, create the file /etc/docker/daemon.json
  2. Add in the following:
    
    {
      "hosts": ["fd://", "tcp://0.0.0.0:2375"]
    }
    
  3. Restart docker and check the docker daemon process. It should have the additional -H flag like so:
    
    $ sudo ps aux | grep dockerd
    root     31239  0.7  0.2 1007880 72816 ?       Ssl  15:03   0:00 /usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375
    

Option 2:

  1. Edit the service by running:
    
    sudo systemctl edit docker.service
    

  2. Add the following lines:
    
    [Service]
    ExecStart=
    ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375
    
  3. Reload the service configuration:
    
    sudo systemctl daemon-reload
  4. Restart the daemon:
    
    sudo systemctl restart docker.service
  5. Use the last step of the previous option to test whether docker is listening on the network port

Feb 20, 2018

Fail2Ban unbanning process

  1. Log into the target machine with Fail2Ban installed
  2. Look at the list of banned IPs and identify the IP you wish to unban:
    sudo iptables -L -n
  3. Discover the names of the jails:
    sudo fail2ban-client status
  4. Unban the IP:
    sudo fail2ban-client set ssh-iptables unbanip 123.123.123.123

Running a new GitLab Runner for private GitLab server

This guide was created in conjunction with the official tutorial.
  1. On your Docker installation, download the Gitlab Runner container. When it is eventually run, there are two volumes that are automatically created: /etc/gitlab-runner and /home/gitlab-runner. You may choose to mount these locally on your host instead.
  2. During the run step, make sure you mount your host's docker socket, either using the -v parameter (-v /var/run/docker.sock:/var/run/docker.sock) or your tool of preference.
  3. Open up a shell console on the Runner container.
  4. Run the registration process (gitlab-runner register) and follow the prompts (the details are listed in the Admin Area under Overview -> Runners)

Feb 19, 2018

Exposing your docker daemon API via network port (and getting it into Portainer)

These instructions will be targeted to Linux installations with systemd installed. In particular, I have used an Ubuntu-flavoured distro (ElementaryOS). I presume you have already installed docker onto your machine.

  1. Stop the Docker daemon if is is already running
    sudo systemctl stop docker.service

  2. You can check the status of the service (including if it is even installed)
    sudo systemctl status docker.service

  3. Open the service configuration file
    sudo nano /lib/systemd/system/docker.service

  4. Find the line with 'ExecStart' and modify it as follows (saving it once complete):
    ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H fd://

  5. Reload all of the daemons:
    sudo systemctl daemon-reload

  6. Start the service
    sudo systemctl start docker.service

  7. Open up your portainer installation, navigate to the 'Endpoints' menu item and then enter in the IP and port for your target computer.

Feb 14, 2018

Navigating a private Docker Repository

All this content can be gained by reading the Registry HTTP API v2 specification.

All URLs are assumed to by appended to https://[hostname|ip]:[port]/

Listing Images

/v2/_catalog

This will output a JSON object in the format of:


{
    "repositories": [
        "image1",
        "image2"
    ]
}

List Tags for Image

/v2/[name]/tags/list

This will output a JSON object in the format of:

{
    "name": "image1",
    "tags": [
        "1.0",
        "latest"
    ]
}

Manifest for an Image

/v2/[name]/manifests/[tag]

This will output a JSON object with the manifest information.

Jan 25, 2018

Interacting with Docker containers

  • Running an interactive shell on the container:
    
    # Your container may have /bin/sh instead
    docker exec -t -i [container] /bin/bash
    

  • Running a command on an image instead (i.e not a running instance):
    
    docker run -t -i [image] /bin/bash
    

  • Get the output of a running container (for logs etc):
    
    docker attach --no-stdin [container]

Cleaning docker

You can use the following bash script to clean out your docker instance:

#!/bin/bash
echo "docker-clean.sh [container|image|volume|cache|data|debugbar]"
if [ "$1" == "container" ]; then
    #Cleaning containers
    docker ps --no-trunc -aqf "status=exited" | xargs docker rm
elif [ "$1" == "image" ]; then
    #Cleaning images
    docker images --no-trunc -aqf "dangling=true" | xargs docker rmi
elif [ "$1" == "volume" ]; then
    #Cleaning volumes
    docker volume ls -qf "dangling=true" | xargs docker volume rm
fi

Jan 23, 2018

Using HSQLDB

These notes are for dealing with the HSQL database used in a legacy web application.

HSQL database commands

Note: These commands are required to by run where the hsqldb jar file is stored

  • Starting the server:
    
    java -cp /var/lib/hsqldb/lib/hsqldb.jar org.hsqldb.Server -database.0 file:mydb -dbname.0 phonebook

  • Start the GUI application for the HSQL database:

    java -cp /var/lib/hsqldb/lib/hsqldb.jar org.hsqldb.util.DatabaseManagerSwing

SQLTOOL

An Example ~/sqltool.rc file:

urlid localhost-sa
url jdbc:hsqldb:hsql://localhost/
username SA
password

To use SQL Tool:
  1. Setup a ~/sqltool.rc to store the connection settings for the database (and run the server).
  2. Once you have started the database (using the commands in the previous section), run the SQLTool command line utility:

    java -cp /var/lib/hsqldb/lib/hsqldb.jar:sqltool.jar org.hsqldb.cmdline.SqlTool

  3. Connect to the database as indication by the urlid in your sqltool.rc file:

    \j localhost-sa

  4. You can stop sqltool at any using

    \xq (table OR SELECT QUERY)

DATABASE INFO

  • Grab list of all tables in the database:

    SELECT TABLE_SCHEM,TABLE_NAME FROM INFORMATION_SCHEMA.SYSTEM_TABLES WHERE TABLE_TYPE = 'TABLE';

  • Grabbing a single employee record:

    SELECT LIMIT 0 1 - FROM EMPLOYEES;

  • Outputting user info as a CSV file:

    SELECT A.USERNAME, A.PASSWORD, A.FIRSTNAME, A.LASTNAME, A.MIDDLENAME as OTHERNAME, B.EMAIL INTO TEXT "users_file" FROM USERS AS A INNER JOIN USER_DETAILS AS B ON A.USERNAME = B.USERNAME;

  • Removing the temporary users table used to create the CSV file:

    DROP TABLE "users";

  • Date based search query where date is a string (DD-MM-YYYY):

    SELECT A.USERNAME, B.FIRSTNAME FROM USERS AS A INNER JOIN EMPLOYEES AS B ON A.USERNAME = B.USERNAME WHERE SUBSTRING(A.EXPIRY,7,4) > 2016;

  • Searching for string partials:

    SELECT TIMES, DATES, USERS FROM LOGS WHERE USERS LIKE 'admin%' AND DATES LIKE '%/03/2015';

  • Grabbing the exact number of matches that match parameters:

    code>SELECT BUILDING, INCTYPE, COUNT(*) AS NUM FROM REPORTS WHERE BUILDING LIKE 'PLACE %' AND DATE LIKE '%/2015' GROUP BY BUILDING, INCTYPE;

Jan 11, 2018

Useful projects to check out (2017 collection)

I have been building a list of projects and packages to check out throughout last year and I decided to consolidate them as a post.

Laravel


  • Laravel Auditing will track changes to Eloquent models
  • Ziggy is a laravel package that outputs router information in JSON form
  • Ardent, a Laravel Model extension

PHP

Vue.js

  • vue-good-wizard is a simple step-by-step wizard plugin
  • vue-directive-tooltip is a simple tooltip plugin

SSL

Postgresql

  • pgBackRest - Back-up
  • Plogical - replication 



Jan 10, 2018

Removing hyphenated text and word-wrapping via CSS

A simple bit of code to use if you do not like the way your browser hyphenates or word wraps your text. (Note the * selector will apply this CSS to ALL elements on the page, and the !important flag will overwrite any later changes.)


* {
 -webkit-hyphens: none !important;
 -ms-hyphens: none !important;
 -o-hyphens: none !important;
 hyphens: none !important;
}

Jan 1, 2018

Using SVG color filters

This is a follow on from my previous post about showing a sprite image using the SVG.js library. We will now add a colour filter to the image using the svg.filter.js plugin.

The color matrix is a 5 column, 4 row matrix. Each row represents an RGBA value, and each column is a multiplier for that particular colour value. Every column represents an RGBA multiplier, which intensifies (or reduces) the resulting colour value based on the original input value. The last column is just a straight value that just gets added on.

So for the following matrix:

1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0

The output image will have the exact same colour value as the input image. This is because the the RGBA multipliers simple times the input RGBA value by 1.

Here are some resources:


The following code shows off the concept.

var col = {
    x: 2,
    y: 0,
}
var draw = SVG('drawing2').size(300, 300)
var clip = draw.clip()
var rect = draw.rect(32, 32)
var image = draw.image('http://icons.iconarchive.com/icons/iconfactory/star-trek-ships/icons-390.jpg')

image.move(-1*(32+5)*col.x-5,-1*(32+5)*col.y-5);

image.filter(function(add) {
  add.colorMatrix('matrix', [ 1.0, 0,   0,   0,   0
                            , 0,   0.2, 0,   0,   0
                            , 0,   0,   0.2, 0,   0
                            , 0,   0,   0,   1.0, 0 ])
})
clip.add(rect)

image.clipWith(clip)