Sunday, 14 March 2021

Running NVIDIA DIGITS Docker container on Ubuntu

Installing NVIDIA DIGITS directly on your computer means that you'll:
  • spend a considerable amount of time in installing all dependencies and building DIGITS itself
  • pollute your machine with another application and its dependencies
To prevent this, we can run NVIDIA DIGITS Docker container. Let's check first whether docker is installed and its version :

$ docker --version
Docker version 20.10.3, build 48d30b5

For the reference, I was running the commands I listed below in this article on my Ubuntu 20.04:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.2 LTS
Release: 20.04
Codename: focal

Ideally, we'd be running NVIDIA Digits on a machine with GPU(s). This would speed up training and inference but Digits can also work on a machine which has a CPU only. 

I have GeForce GT 640 graphics card:

$ nvidia-smi -L
GPU 0: GeForce GT 640 (UUID: GPU-f2583df9-404d-2564-d332-e7878a94d087)

$ lspci
...
VGA compatible controller: NVIDIA Corporation GK107 [GeForce GT 640 OEM] (rev a1)
...

GK107 is a code name for GeForce GT 640 (GDDR5) (source: GeForce 600 series - Wikipedia) which, according to CUDA GPUs | NVIDIA Developer, has computing capability 3.5 (which is supported as it has to be >2.1 according to Installation Guide — NVIDIA Cloud Native Technologies documentation).

To test the local GPU we can run nvidia-smi application on the local host or in Docker image.

If we haven't installed CUDA or nvidia-smi locally, we can run nvidia-smi from NVIDIA CUDA Docker image:

$ sudo docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi
Thu Feb 11 01:02:09 2021       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.32.03    Driver Version: 460.32.03    CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GT 640      Off  | 00000000:01:00.0 N/A |                  N/A |
| 40%   31C    P8    N/A /  N/A |    286MiB /  1992MiB |     N/A      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+


Let's now follow the instructions from DIGITS | NVIDIA NGC. We first need to download the image to our local host:

$ docker pull nvcr.io/nvidia/digits:20.12-tensorflow-py3
20.12-tensorflow-py3: Pulling from nvidia/digits
6a5697faee43: Pulling fs layer 
ba13d3bc422b: Pulling fs layer 
...
cec6045b0d0e: Pulling fs layer 
cb4aa708e833: Waiting 
235cfa23a5f4: Waiting 
24781a3c82ea: Waiting 
f7c7d47c1a97: Pull complete 
...
b57dde2f2923: Pull complete 
Digest: sha256:7542143bc2292fc48a3874786877815a5ca6a74a69366324aaf66914155cb5a7
Status: Downloaded newer image for nvcr.io/nvidia/digits:20.12-tensorflow-py3
nvcr.io/nvidia/digits:20.12-tensorflow-py3

Let's now run the container. docker run has --gpus option which instructs Docker to add GPU devices to container ('all' to pass all GPUs).

$ docker run --gpus all -it --rm nvcr.io/nvidia/digits:20.12-tensorflow-py3
docker: Error response from daemon: could not select device driver "" with capabilities: [[gpu]].

I haven't installed NVIDIA Container Toolkit (nvidia-docker) which enable Docker containers accessing host's GPU. Installation Guide — NVIDIA Cloud Native Technologies documentation describes how to install it:

$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
   && curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - \
   && curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
$ sudo apt-get update
$ sudo apt-get install -y nvidia-docker2
$ sudo systemctl restart docker


$ nvidia-docker version 
NVIDIA Docker: 2.5.0
Client: Docker Engine - Community
 Version:           20.10.3
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        48d30b5
 Built:             Fri Jan 29 14:33:21 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.3
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       46229ca
  Built:            Fri Jan 29 14:31:32 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.3
  GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
 runc:
  Version:          1.0.0-rc92
  GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0


To be on the safe side, I also installed the latest NVIDIA driver.

$ sudo docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi
[sudo] password for bojan: 
Thu Feb 11 01:02:09 2021       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.32.03    Driver Version: 460.32.03    CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GT 640      Off  | 00000000:01:00.0 N/A |                  N/A |
| 40%   31C    P8    N/A /  N/A |    286MiB /  1992MiB |     N/A      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+


This time running DIGITS container was successful. DIGITS 6.0 http server uses port 5000 by default and in this example it is mapped to host port 8888.

$ docker run --gpus all -it --rm -p 8888:5000 nvcr.io/nvidia/digits:20.12-tensorflow-py3

============
== DIGITS ==
============

NVIDIA Release 20.12 (build 17912121)
DIGITS Version 6.1.1

Container image Copyright (c) 2020, NVIDIA CORPORATION.  All rights reserved.
DIGITS Copyright (c) 2014-2019, NVIDIA CORPORATION. All rights reserved.

Various files include modifications (c) NVIDIA CORPORATION.  All rights reserved.
NVIDIA modifications are covered by the license terms that apply to the underlying project or file.
ERROR: No supported GPU(s) detected to run this container

  ___ ___ ___ ___ _____ ___
 |   \_ _/ __|_ _|_   _/ __|
 | |) | | (_ || |  | | \__ \
 |___/___\___|___| |_| |___/ 6.1.1

Caffe support disabled.
Reason: A valid Caffe installation was not found on your system.
cudaRuntimeGetVersion() failed with error #999
2021-02-11 16:23:54.454747: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.11.0
WARNING:tensorflow:Deprecation warnings have been disabled. Set TF_ENABLE_DEPRECATION_WARNINGS=1 to re-enable them.
/opt/digits/digits/pretrained_model/views.py:32: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if str(files['weights_file'].filename) is '':
/opt/digits/digits/pretrained_model/views.py:38: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if str(files['model_def_file'].filename) is '':
/opt/digits/digits/pretrained_model/views.py:54: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if str(files['weights_file'].filename) is '':
/opt/digits/digits/pretrained_model/views.py:60: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if str(files['model_def_file'].filename) is '':
/opt/digits/digits/pretrained_model/views.py:169: SyntaxWarning: "is" with a literal. Did you mean "=="?
  elif str(flask.request.form['job_name']) is '':
/opt/digits/digits/pretrained_model/views.py:177: SyntaxWarning: "is not" with a literal. Did you mean "!="?
  if str(flask.request.files['labels_file'].filename) is not '':
2021-02-11 16:23:56 [INFO ] Loaded 0 jobs.


If we now open a browser on the host and type http://localhost:8888 we'll be able to see DIGITS home page:



As DIGITS is a web-based application we don't need to run it in interactive mode (docker run -it) but can run it in a detached mode (docker run -d):

$ docker run \
--gpus all \
-d \
--name digits \
--rm \
-p 8888:5000 \
-v /home/bojan/dev/digits-demo/data:/data \
-v /home/bojan/dev/digits-demo/jobs:/workspace/jobs \ nvcr.io/nvidia/digits:20.12-tensorflow-py3

905f9a8c8e48bc87ae99117eed92b855d45c7d37695c0e94433bd18fab6bfaca

We can verify that DIGITS container is indeed running:

$ docker ps 
CONTAINER ID   IMAGE                                        COMMAND                  CREATED              STATUS              PORTS                                                  NAMES
905f9a8c8e48   nvcr.io/nvidia/digits:20.12-tensorflow-py3   "/usr/local/bin/nvid…"   About a minute ago   Up About a minute   6006/tcp, 6064/tcp, 8888/tcp, 0.0.0.0:8888->5000/tcp   digits


Why DIGITS doesn't recognize my GPU?



One thing didn't seem right to me though. In the upper right corner of the DIGITS home page should be a text which indicates how many GPUs are available. In my case, although I have one GPU, no GPUs were listed. 




I tried first to check if GPU is indeed visible from the container:

$ docker exec -it digits bash
root@e58b860504a9:/workspace# 

root@e58b860504a9:/workspace# nvidia-smi
Fri Feb 12 23:33:17 2021       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.32.03    Driver Version: 460.32.03    CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GT 640      Off  | 00000000:01:00.0 N/A |                  N/A |
| 40%   32C    P8    N/A /  N/A |    260MiB /  1992MiB |     N/A      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

Graphics card was visible. DIGITS installation contains a Python script which is DIGITS Device Query (source code: python/9427/DIGITS/digits/device_query.py). When I tried to run it, I got an error:

root@e58b860504a9:/opt/digits/digits# python device_query.py 
cudaRuntimeGetVersion() failed with error #999
No devices found.


cudaErrorUnknown = 999
This indicates that an unknown internal error has occurred.
CUDA was installed fine:

root@6cd6c429f20c:/workspace# nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2020 NVIDIA Corporation
Built on Mon_Oct_12_20:09:46_PDT_2020
Cuda compilation tools, release 11.1, V11.1.105
Build cuda_11.1.TC455_06.29190527_0


On the host system I checked if loading the NVIDIA driver gave any errors (NVRM errors are internal to the nvidia kernel module):

$ sudo dmesg |grep NVRM
[sudo] password for bojan: 
[    2.283911] NVRM: loading NVIDIA UNIX x86_64 Kernel Module  460.32.03  Sun Dec 27 19:00:34 UTC 2020
[ 8654.742795] NVRM: GPU at PCI:0000:01:00: GPU-f2583df9-404d-2564-d332-e7878a94d087
[ 8654.742800] NVRM: Xid (PCI:0000:01:00): 31, pid=577, Ch 00000002, intr 10000000. MMU Fault: ENGINE HOST4 HUBCLIENT_HOST faulted @ 0x1_01160000. Fault is of type FAULT_INFO_TYPE_UNSUPPORTED


I could not deduct anything useful from here but by reading DIGITS release notes I finally found the reason why DIGITS won't recognize my GPU - it is too old!

Installation Guide — NVIDIA Cloud Native Technologies documentation specifies compute capability requirements for NVIDIA Container Toolkit but compute capability requirements for DIGITS Docker image are specified for each image release. For digits:20.12 DIGITS Release Notes :: NVIDIA Deep Learning DIGITS Documentation states the following:

Release 20.12 supports CUDA compute capability 6.0 and higher.

My GPU has compute capability 3.5 and so it does not meet that requirement.


References







Friday, 12 March 2021

How to install MySQL Workbench on Ubuntu

Ubuntu 20.04 in my case.

We first need to download the deb package from the Oracle website:


We can choose package which does not contain debugging information files (dbgsym).

Read How to check file GPG signature on Linux | My Public Notepad and don't skip that step! Now we can try to install the package:

$ sudo dpkg -i mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb 
Selecting previously unselected package mysql-workbench-community.
(Reading database ... 304721 files and directories currently installed.)
Preparing to unpack mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb ...
Unpacking mysql-workbench-community (8.0.23-1ubuntu20.04) ...
dpkg: dependency problems prevent configuration of mysql-workbench-community:
 mysql-workbench-community depends on libzip5 (>= 0.10); however:
  Package libzip5 is not installed.

dpkg: error processing package mysql-workbench-community (--install):
 dependency problems - leaving unconfigured
Processing triggers for gnome-menus (3.36.0-1ubuntu1) ...
Processing triggers for desktop-file-utils (0.24-1ubuntu3) ...
Processing triggers for mime-support (3.64ubuntu1) ...
Processing triggers for hicolor-icon-theme (0.17-2) ...
Processing triggers for shared-mime-info (1.15-1) ...
Errors were encountered while processing:
 mysql-workbench-community


To fix the dependencies:

$  sudo apt --fix-broken install
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Correcting dependencies... Done
The following additional packages will be installed:
  libzip5
The following NEW packages will be installed
  libzip5
0 to upgrade, 1 to newly install, 0 to remove and 38 not to upgrade.
1 not fully installed or removed.
Need to get 46.7 kB of archives.
After this operation, 134 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://gb.archive.ubuntu.com/ubuntu focal/universe amd64 libzip5 amd64 1.5.1-0ubuntu1 [46.7 kB]
Fetched 46.7 kB in 1s (75.4 kB/s)
Selecting previously unselected package libzip5:amd64.
(Reading database ... 306005 files and directories currently installed.)
Preparing to unpack .../libzip5_1.5.1-0ubuntu1_amd64.deb ...
Unpacking libzip5:amd64 (1.5.1-0ubuntu1) ...
Setting up libzip5:amd64 (1.5.1-0ubuntu1) ...
Setting up mysql-workbench-community (8.0.23-1ubuntu20.04) ...
Processing triggers for libc-bin (2.31-0ubuntu9.2) ...

Installation is now successful:

$ sudo dpkg -i mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb 
(Reading database ... 306011 files and directories currently installed.)
Preparing to unpack mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb ...
Unpacking mysql-workbench-community (8.0.23-1ubuntu20.04) over (8.0.23-1ubuntu20.04) ...
Setting up mysql-workbench-community (8.0.23-1ubuntu20.04) ...
Processing triggers for gnome-menus (3.36.0-1ubuntu1) ...
Processing triggers for desktop-file-utils (0.24-1ubuntu3) ...
Processing triggers for mime-support (3.64ubuntu1) ...
Processing triggers for hicolor-icon-theme (0.17-2) ...
Processing triggers for shared-mime-info (1.15-1) ...

Let's try to run it:

$ mysql-workbench --version
Workbench can't find libproj.so, some options may be unavailable.
MySQL Workbench CE (GPL) 8.0.23 CE build 365764

For this warning, see here: MySQL Workbench not opening on ubuntu - Stack Overflow

That library is not present on my machine:

$ sudo find / -name libproj.so

But as one comment said, it is optional so the app worked fine:


Updating Workbench to the latest version


I had version 8.0.23 and check for updates returned 8.0.27. I downloaded the deb file and procedure was the same as for first installation:

$ sudo dpkg -i mysql-workbench-community_8.0.27-1ubuntu20.04_amd64.deb
(Reading database ... 304933 files and directories currently installed.)
Preparing to unpack mysql-workbench-community_8.0.27-1ubuntu20.04_amd64.deb ...
Unpacking mysql-workbench-community (8.0.27-1ubuntu20.04) over (8.0.23-1ubuntu20.04) ...
dpkg: dependency problems prevent configuration of mysql-workbench-community:
 mysql-workbench-community depends on libproj15 (>= 6.3.0); however:
  Package libproj15 is not installed.

dpkg: error processing package mysql-workbench-community (--install):
 dependency problems - leaving unconfigured

Processing triggers for gnome-menus (3.36.0-1ubuntu1) ...
Processing triggers for desktop-file-utils (0.24-1ubuntu3) ...
Processing triggers for mime-support (3.64ubuntu1) ...
Processing triggers for hicolor-icon-theme (0.17-2) ...
Processing triggers for shared-mime-info (1.15-1) ...
Errors were encountered while processing:
 mysql-workbench-community

 
$ sudo apt --fix-broken install
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Correcting dependencies... Done
The following additional packages will be installed:
  libproj15 proj-data
Suggested packages:
  proj-bin
The following NEW packages will be installed
  libproj15 proj-data
0 to upgrade, 2 to newly install, 0 to remove and 0 not to upgrade.
1 not fully installed or removed.
Need to get 8,572 kB of archives.
After this operation, 27.5 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://gb.archive.ubuntu.com/ubuntu focal/universe amd64 proj-data all 6.3.1-1 [7,647 kB]
Get:2 http://gb.archive.ubuntu.com/ubuntu focal/universe amd64 libproj15 amd64 6.3.1-1 [925 kB]
Fetched 8,572 kB in 4s (2,323 kB/s)
Selecting previously unselected package proj-data.
(Reading database ... 304934 files and directories currently installed.)
Preparing to unpack .../proj-data_6.3.1-1_all.deb ...
Unpacking proj-data (6.3.1-1) ...
Selecting previously unselected package libproj15:amd64.
Preparing to unpack .../libproj15_6.3.1-1_amd64.deb ...
Unpacking libproj15:amd64 (6.3.1-1) ...
Setting up proj-data (6.3.1-1) ...
Setting up libproj15:amd64 (6.3.1-1) ...
Setting up mysql-workbench-community (8.0.27-1ubuntu20.04) ...
Processing triggers for libc-bin (2.31-0ubuntu9.2) ...
 
$ sudo dpkg -i mysql-workbench-community_8.0.27-1ubuntu20.04_amd64.deb
(Reading database ... 304985 files and directories currently installed.)
Preparing to unpack mysql-workbench-community_8.0.27-1ubuntu20.04_amd64.deb ...
Unpacking mysql-workbench-community (8.0.27-1ubuntu20.04) over (8.0.27-1ubuntu20.04) ...
Setting up mysql-workbench-community (8.0.27-1ubuntu20.04) ...
Processing triggers for gnome-menus (3.36.0-1ubuntu1) ...
Processing triggers for desktop-file-utils (0.24-1ubuntu3) ...
Processing triggers for mime-support (3.64ubuntu1) ...
Processing triggers for hicolor-icon-theme (0.17-2) ...
Processing triggers for shared-mime-info (1.15-1) ...

 

Checking for the updates now shows that no new updates are available:


Thursday, 11 March 2021

How to fix an issue when Ubuntu does not automatically recognise plugged in headphones

I've been using recently a Bluetooth headphones on my Ubuntu 20.04. But then I noticed that when I'd plug in my wired headphones, I wouldn't get that usual Select Audio Device dialog:


In the Sound Settings, the Output Device would be set to Dummy Output:


Restarting the pulseaudio application is what helped me resolve this issue:

$ pulseaudio --kill && pulseaudio --start


No reboot was required.

Monday, 22 February 2021

How to check file GPG signature on Linux

Retrieving and installation of Linux software can be done via package management systems (like apt) or manually downloading and running the executable. 

Public Key Cryptography is used on Linux to verify the identity of the file provider. A public and private key are created, file sender signs the file with private key and publishes public key. Receiver uses the public key to verify the signature. 

GNU Privacy Guard (GnuPG or GPG) is set of open-source tools for key exchange, signing, verification, encrypting and decrypting files.




I want to show here how to use gpg to verify a file downloaded from Internet on the example of Tor browser installer. It is not necessary to do this when using package managers to install some software as they use this automatically (see SecureApt - Debian Wiki).

From Tor's website we need to download:

1) an installation archive (https://www.torproject.org/dist/torbrowser/10.0.10/tor-browser-linux64-10.0.10_en-US.tar.xz) 

2) its PGP signature (https://dist.torproject.org/torbrowser/10.0.10/tor-browser-linux64-10.0.10_en-US.tar.xz.asc) which looks like this:

-----BEGIN PGP SIGNATURE-----

iQIcBAABCgAGBQJgG5QbAAoJEOt3RJHZ/wbinYQP/RVTZOY9YLe7y2azJs2K3IcF
Wr20e2zMGVGpvV2WZ3uYij7nMQGujPU4V8cJ/5vSb3ONKSwr/YXfqSGOC6gL8EAF
yvJsS5JjVdGYpBxg9ye1Cb3AFtKgQLBwFLvDWtWuLH1lk7VDLY5dDPbRpX6x3HKE
LbulR1IsUdQLdrbUfkoUpYBf1GTb+4+F4a//tLEnCwWojO72jL3UNzRmKPCOZ+KD
IxbGxHCtqAHMU8dBYZ5yQugW7mLnzmTAyFtfem9KuJXMFlpKICadOK6yZDYH2U3Y
SPGONS3vRZEhlfuDmyh0U18L7A3H25ELdbUTYhQLfZj0/OCy0cyqESzn65cpSPS8
/Fg2BdnmyzkWqo45KF5l4KCoT3+Jd2BHC6uZ8na/LpW6WncQ43+8obBbthgHODLA
rHGTTA7ccR+2aWFHjlZaGHErj7mjdRiNuOzlpWVpfMwcvMoCxN71DUxrVIdQchY4
j7K4fvt5ZpR3Ln0pFd9QgbwES6bqi2U0MZ872ChQ/LPoYhGxqrsEpJTJoZBnJ7Ik
IL4MiHLcN+9RKwamBhC9qjOp9V1IHVexxPvRxWc1Ix3Vwp5W7hFSZoM+pBruMjsF
9gc8pEL3RETBomr8zSGRPKDAvHv/V+r31pHTEr9pgEn3rN7wU7VCABVrK7pHJltl
03nm5LAu7HdLxFYysxur
=6o0j
-----END PGP SIGNATURE-----

Now we want to verify the authenticity of the downloaded file. The tool that we're going to use is gpg. gpg is included in Linux distributions (I am using Ubuntu).

$ gpg --help
gpg (GnuPG) 2.2.19
libgcrypt 1.8.5
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /home/bojan/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cypher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

Syntax: gpg [options] [files]
Sign, check, encrypt or decrypt
Default operation depends on the input data

Commands:
 
 -s, --sign                  make a signature
     --clear-sign            make a clear text signature
 -b, --detach-sign           make a detached signature
 -e, --encrypt               encrypt data
 -c, --symmetric             encryption only with symmetric cypher
 -d, --decrypt               decrypt data (default)
     --verify                verify a signature
 -k, --list-keys             list keys
     --list-signatures       list keys and signatures
     --check-signatures      list and check key signatures
     --fingerprint           list keys and fingerprints
 -K, --list-secret-keys      list secret keys
     --generate-key          generate a new key pair
     --quick-generate-key    quickly generate a new key pair
     --quick-add-uid         quickly add a new user-id
     --quick-revoke-uid      quickly revoke a user-id
     --quick-set-expire      quickly set a new expiration date
     --full-generate-key     full featured key pair generation
     --generate-revocation   generate a revocation certificate
     --delete-keys           remove keys from the public keyring
     --delete-secret-keys    remove keys from the secret keyring
     --quick-sign-key        quickly sign a key
     --quick-lsign-key       quickly sign a key locally
     --sign-key              sign a key
     --lsign-key             sign a key locally
     --edit-key              sign or edit a key
     --change-passphrase     change a passphrase
     --export                export keys
     --send-keys             export keys to a keyserver
     --receive-keys          import keys from a keyserver
     --search-keys           search for keys on a keyserver
     --refresh-keys          update all keys from a keyserver
     --import                import/merge keys
     --card-status           print the card status
     --edit-card             change data on a card
     --change-pin            change a card's PIN
     --update-trustdb        update the trust database
     --print-md              print message digests
     --server                run in server mode
     --tofu-policy VALUE     set the TOFU policy for a key

Options:
 
 -a, --armor                 create ASCII armoured output
 -r, --recipient USER-ID     encrypt for USER-ID
 -u, --local-user USER-ID    use USER-ID to sign or decrypt
 -z N                        set compress level to N (0 disables)
     --textmode              use canonical text mode
 -o, --output FILE           write output to FILE
 -v, --verbose               verbose
 -n, --dry-run               do not make any changes
 -i, --interactive           prompt before overwriting
     --openpgp               use strict OpenPGP behaviour

(See the man page for a complete listing of all commands and options)

Examples:

 -se -r Bob [file]          sign and encrypt for user Bob
 --clear-sign [file]        make a clear text signature
 --detach-sign [file]       make a detached signature
 --list-keys [names]        show keys
 --fingerprint [names]      show fingerprints

Please report bugs to <https://bugs.gnupg.org>.


From Tor's website we can find that it is signed by Tor Browser Developers and that their signing key is 0xEF6E286DDA85EA2A4BA7DE684E2C6E8793298290. 

gpg can import the public GPG (build) key of the author (which is usually named after author's email) from Web Key Directory it deducts from author's public email ID:

$ gpg --auto-key-locate nodefault,wkd --locate-keys torbrowser@torproject.org
gpg: key 4E2C6E8793298290: public key "Tor Browser Developers (signing key) <torbrowser@torproject.org>" imported
gpg: Total number processed: 1
gpg:               imported: 1
pub   rsa4096 2014-12-15 [C] [expires: 2025-07-21]
      EF6E286DDA85EA2A4BA7DE684E2C6E8793298290
uid           [ unknown] Tor Browser Developers (signing key) <torbrowser@torproject.org>
sub   rsa4096 2018-05-26 [S] [expires: 2021-06-12]

--auto-key-locate defines how will gpg locate keys:

--auto-key-locate mechanisms
--no-auto-key-locate
              GnuPG  can  automatically  locate  and  retrieve  keys  as  needed using this option.  This happens when encrypting to an email address (in the "user@example.com" form), and there are no "user@example.com" keys on the local keyring.  This option takes any number of the mechanisms listed below, in the order they are to be tried.  Instead of listing the mechanisms as comma delimited  arguments,  the  option  may also be given several times to add more mechanism.  The option --no-auto-key-locate or the mechanism "clear" resets the list.  The default is "local,wkd".

              cert   Locate a key using DNS CERT, as specified in RFC-4398.
              pka    Locate a key using DNS PKA.
              dane   Locate a key using DANE, as specified in draft-ietf-dane-openpgpkey-05.txt.
              wkd    Locate a key using the Web Key Directory protocol.
              ldap   Using DNS Service Discovery, check the domain in question for any LDAP keyservers to use.  If this fails, attempt to locate the key using the  PGP  Universal  method  of  checking ‘ldap://keys.(thedomain)’.
              keyserver Locate a key using a keyserver.
              keyserver-URL In addition, a keyserver URL as used in the dirmngr configuration may be used here to query that particular keyserver.
              local  Locate  the  key  using  the  local keyrings.  This mechanism allows the user to select the order a local key lookup is done.  Thus using ‘--auto-key-locate local’ is identical to --no-auto-key-locate.
              nodefault
                     This flag disables the standard local key lookup, done before any of the mechanisms defined by the --auto-key-locate are tried.  The position of this mechanism in  the  list  does not matter.  It is not required if local is also used.
              clear  Clear all defined mechanisms.  This is useful to override mechanisms given in a config file.  Note that a nodefault in mechanisms will also be cleared unless it is given after the clear.


Web Key Directories: 
  • provide an easy way to discover public keys through HTTPS
  • way to retrieve PGP keys without using a keyserver
  • keep email addresses private (as you need to know an address already to ask for a public key)
  • are authoritative source for a public key for its domain
Web Key Directory protocol:
  • The sender's mail client checks a "well known" URL on the domain of the recipient.
  • If a public key is available for that mail address, will be downloaded via HTTPS.
  • The downloaded pubkey can now be used without further user interaction.
How does gpg find the URL of the key? It uses the host name from the provided email address and constructs it. See here: Attacks on GnuPG's Web Key Directory (WKD) | SektionEins GmbH and here: draft-koch-openpgp-webkey-service-08 - OpenPGP Web Key Directory.

torbrowser@torproject.org =>
    username (local-part): torbrowser
    domain: torproject.org

$ echo -n "torbrowser" | sha1sum 
5426242bb720dfdd6dc01bfcddfe3f5b92d9f065 

I used Cryptii online tool which uses z-base-32 Encoder to encrypt the string (sha1 sum):

kounek7zrdx745qydx6p59t9mqjpuhdf

We now have zbase32 username.

Every domain can host host the WKD from a special openpgpkey subdomain: openpgpkey.

It should have a folder structure similar to this:

.well-known
.well-known/openpgpkey
.well-known/openpgpkey/example.com
.well-known/openpgpkey/example.com/policy
.well-known/openpgpkey/example.com/hu
.well-known/openpgpkey/example.com/hu/<zbase32_username>

In our case:

https://openpgpkey.torproject.org/.well-known/openpgpkey/torproject.org/hu/kounek7zrdx745qydx6p59t9mqjpuhdf

We can see that Tor Browser Developers (signing key) is at that URL (I'm printing here only first 4 lines of the output): 

$ curl https://openpgpkey.torproject.org/.well-known/openpgpkey/hu/kounek7zrdx745qydx6p59t9mqjpuhdf?l=torbrowser \
--output - \
| head -n4
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
T����2ϐ��,�c݉0�     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
� Jʸ�X�jV_���>�P�AD���2B�>[���Ԟrv�K
Ge�ӧGWo��31�S#�2�H�+Յ5�Y�8{�Պbq'M�����D\l}�~9�S!/�v(�w�(;���bX/���@Tor Browser Developers (signing key) <torbrowser@torproject.org>�Te%�G�'��`;��X�;D�5q_���¤�f=��N��)�>�'�n}��K��TGhi��7;����[�
>
 
  5025    0     0  14075      0 --:--:-- --:--:-- --:--:-- 14036
(23) Failed writing body

Let's save this key in tor.keyring file:

$ gpg --output ./tor.keyring --export 0xEF6E286DDA85EA2A4BA7DE684E2C6E8793298290


Verification of the signature is done via gpgv tool:

$ gpgv --help
gpgv (GnuPG) 2.2.19
libgcrypt 1.8.5
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Syntax: gpgv [options] [files]
Check signatures against known trusted keys


Options:
 
 -v, --verbose                verbose
 -q, --quiet                  be somewhat more quiet
     --keyring FILE           take the keys from the keyring FILE
 -o, --output FILE            write output to FILE
     --ignore-time-conflict   make timestamp conflicts only a warning
     --status-fd FD           write status info to this FD
     --weak-digest ALGO       reject signatures made with ALGO

Please report bugs to <https://bugs.gnupg.org>.


$ gpgv \
--keyring ./tor.keyring \
~/Downloads/tor-browser-linux64-10.0.10_en-US.tar.xz.asc \ ~/Downloads/tor-browser-linux64-10.0.10_en-US.tar.xz
gpgv: Signature made Thu 04 Feb 2021 06:28:43 GMT
gpgv:                using RSA key EB774491D9FF06E2
gpgv: Good signature from "Tor Browser Developers (signing key) <torbrowser@torproject.org>"


What if author does not support Web Key Directory?


This is the case with e.g. Oracle. MySQL :: MySQL 8.0 Reference Manual :: 2.1.4.2 Signature Checking Using GnuPG states that the public build GPG key name is mysql-build@oss.oracle.com but WKD search fails:

$ gpg --auto-key-locate nodefault,wkd --locate-keys mysql-build@oss.oracle.com
gpg: error retrieving 'mysql-build@oss.oracle.com' via WKD: No data
gpg: error reading key: No data

In this case the author might provide (on their website) the public key which can be downloaded or copy-pasted into a locally stored file. Oracle provides this key in this way and we can save it in a file named e.g. mysql_pubkey.pub:

$ cat mysql_pubkey.pub 
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1

mQGiBD4+owwRBAC14GIfUfCyEDSIePvEW3SAFUdJBtoQHH/nJKZyQT7h9bPlUWC3
RODjQReyCITRrdwyrKUGku2FmeVGwn2u2WmDMNABLnpprWPkBdCk96+OmSLN9brZ
fw2vOUgCmYv2hW0hyDHuvYlQA/BThQoADgj8AW6/0Lo7V1W9/8VuHP0gQwCgvzV3
BqOxRznNCRCRxAuAuVztHRcEAJooQK1+iSiunZMYD1WufeXfshc57S/+yeJkegNW
...
roUTAKClYAhZuX2nUNwH4vlEJQHDqYa5yQ==
=ghXk
-----END PGP PUBLIC KEY BLOCK-----

Before importing this public key, we want to verify that it hasn't been tampered on its way from issuer to us. The file author/issuer usually displays (on the file download page) the public key fingerprint (thumbrint) which is basically a unique, shortened version (hash) of the full key. gpg tool can extract the fingerprint from the public key without importing it. 

From the man gpg:

       --show-keys
              This commands takes OpenPGP keys as input and prints information
              about  them in the same way the command --list-keys does for lo‐
              cally stored key.  In addition the list  options  show-unusable-
              uids, show-unusable-subkeys, show-notations and show-policy-urls
              are also enabled.  As usual for automated processing, this  com‐
              mand should be combined with the option --with-colons.         
        --fingerprint
              List  all  keys (or the specified ones) along with their finger‐
              prints. This is the same output as --list-keys but with the  ad‐
              ditional output of a line with the fingerprint. May also be com‐
              bined with --check-signatures.  If this command is given  twice,
              the  fingerprints  of  all  secondary keys are listed too.  This
              command also forces pretty printing of fingerprints if the keyid
              format has been set to "none".

--show-keys is used for not-yet-imported keys while --fingerprint is used for keys already imported in the local keyring.

The fingerprint in the output of 

$ gpg --show-keys package-signing-key.pub
pub   rsa4096 2022-08-18 [SC]
      59C86188E22ABB19BD5540477B04A1B8DD79B481
uid                      Zoom Video Communications, Inc. <CryptoOpsCodeSignProd@zoom.us>
sub   rsa2048 2022-08-18 [A]
sub   rsa2048 2022-08-18 [E]


...should match the fingerprint shown on the file's download page. This example was for Zoom's rpm packages. 

Note that we can pass the public key string instead of file to gpg:

$ gpg --fingerprint <<EOT
-----BEGIN PGP PUBLIC KEY BLOCK-----

AwIDFQIDAxYCAQIeAQIXgAAKCRCf8lmA9bp+T/G/AKDM1QDs7il/CJhTycgDvE3c
cmlubyA8aGlyYW1AaGlyYW1jaGlyaW5vLmNvbT6IWwQTEQIAGwUCQ+ylKwYLCQgH
...
=RBPl
-----END PGP PUBLIC KEY BLOCK-----
EOT 

Now we can import this public key to our local public GPG keyring:

$ gpg --import mysql_pubkey.pub
gpg: key 8C718D3B5072E1F5: 75 signatures not checked due to missing keys
gpg: key 8C718D3B5072E1F5: public key "MySQL Release Engineering <mysql-build@oss.oracle.com>" imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg: no ultimately trusted keys found

This keyring is stored in ~/.gnupg/ directory (which can be copied to another machine):

$ ls ~/.gnupg/ 
crls.d  private-keys-v1.d  pubring.kbx  pubring.kbx~  trustdb.gpg

We can list all currently added public keys in it:

$ gpg --list-keys
/home/bojan/.gnupg/pubring.kbx
------------------------------
pub   rsa4096 2014-12-15 [C] [expires: 2025-07-21]
      EF6E286DDA85EA2A4BA7DE684E2C6E8793298290
uid           [ unknown] Tor Browser Developers (signing key) <torbrowser@torproject.org>
sub   rsa4096 2018-05-26 [S] [expires: 2021-06-12]

pub   dsa1024 2003-02-03 [SCA] [expires: 2022-02-16]
      A4A9406876FCBD3C456770C88C718D3B5072E1F5
uid           [ unknown] MySQL Release Engineering <mysql-build@oss.oracle.com>

After importing Zoom's key:

$ gpg --fingerprint 
/home/bojan/.gnupg/pubring.kbx
------------------------------
pub   rsa4096 2014-12-15 [C] [expires: 2025-07-21]
      EF6E 286D DA85 EA2A 4BA7  DE68 4E2C 6E87 9329 8290
uid           [ unknown] Tor Browser Developers (signing key) <torbrowser@torproject.org>

pub   dsa1024 2003-02-03 [SCA] [expired: 2022-02-16]
      A4A9 4068 76FC BD3C 4567  70C8 8C71 8D3B 5072 E1F5
uid           [ expired] MySQL Release Engineering <mysql-build@oss.oracle.com>

pub   rsa4096 2022-08-18 [SC]
      59C8 6188 E22A BB19 BD55  4047 7B04 A1B8 DD79 B481
uid           [ unknown] Zoom Video Communications, Inc. <CryptoOpsCodeSignProd@zoom.us>
sub   rsa2048 2022-08-18 [A]
sub   rsa2048 2022-08-18 [E]


I have both an Oracle's binary file and its GPG key in the same directory:

$ ls mysql-workbench-community_8.0.23-1ubuntu20.04_amd64*
mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb  
mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb.asc

...and can verify it now:

$ gpg --verify mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb.asc 
gpg: assuming signed data in 'mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb'
gpg: Signature made Fri 18 Dec 2020 07:56:43 GMT
gpg:                using DSA key A4A9406876FCBD3C456770C88C718D3B5072E1F5
gpg:                issuer "mysql-build@oss.oracle.com"
gpg: Good signature from "MySQL Release Engineering <mysql-build@oss.oracle.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: A4A9 4068 76FC BD3C 4567  70C8 8C71 8D3B 5072 E1F5


 
That is normal, as they depend on your setup and configuration. Here are explanations for these warnings:

gpg: no ultimately trusted keys found: This means that the specific key is not "ultimately trusted" by you or your web of trust, which is okay for the purposes of verifying file signatures.

WARNING: This key is not certified with a trusted signature! There is no indication that the signature belongs to the owner.: This refers to your level of trust in your belief that you possess our real public key. This is a personal decision. Ideally, a MySQL developer would hand you the key in person, but more commonly, you downloaded it. Was the download tampered with? Probably not, but this decision is up to you. Setting up a web of trust is one method for trusting them.

So the last warning actually tells us that the public PGP key might have been tampered/modified when we were copy/pasting it from the web page to our local file. Was web page authentic? Did we by any chance modified the content of the key in the mysql_pubkey.pub file before we imported it to our local keychain? Everything is possible but if we believe the original key has been imported, we can ignore this warning.


Fetching public GPG key by its ID


We showed above how to fetch key from WKD server if we know its name in the form of email address or if we have the content of the key. There is the third way how we can fetch the key, it's if we know its ID which might be published on the author's website:

We can download the key from the public keyserver using the public key id, 5072E1F5:

$ gpg --recv-keys 5072E1F5


References: