trents_blog/site/search/search_index.json

1 line
273 KiB
JSON

{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"","title":"Home"},{"location":"links/","text":"Trent's Blog Links Home RSS Source For This Blog AudioBooks Attention Span History GitHub Twitter Facebook Trent Docs Hugo Themes Report libre_gps_parser Concise PDX Free Code Camp Challenges Device Layout Oregon Hikers' Field Guide","title":"Links"},{"location":"links/#trents-blog","text":"","title":"Trent's Blog"},{"location":"links/#links","text":"Home RSS Source For This Blog AudioBooks Attention Span History GitHub Twitter Facebook Trent Docs Hugo Themes Report libre_gps_parser Concise PDX Free Code Camp Challenges Device Layout Oregon Hikers' Field Guide","title":"Links"},{"location":"rss/","text":"Trent's Blog RSS Created Updated Links Home Links","title":"RSS"},{"location":"rss/#trents-blog","text":"","title":"Trent's Blog"},{"location":"rss/#rss","text":"Created Updated","title":"RSS"},{"location":"rss/#links","text":"Home Links","title":"Links"},{"location":"posts/add-kvm-network-with-virsh/","text":"date: 2021-10-16 Introduction This is a short and sweet walk-through for how to create a new network for libvirt for kvm , from the command line, using virsh . Name Resolution Let's start with name resolution. Install libnss-libvirt : apt install libnss-libvirt In /etc/nsswitch.conf , add libvirt to hosts key. # /etc/nsswitch.conf # change this ... hosts: files dns mymachines ... # to this ... hosts: files libvirt dns mymachines ... Starter XML You could dumpxml on the existing default network: virsh net-dumpxml default > foonet.xml Then, edit foonet.xml: remove the network uuid change the network name to taste remove the bridge mac change the bridge name to taste change the bridge ip address and dhcp range to taste <!-- foonet.xml --> <network> <name> foonet </name> <forward mode= 'nat' > <nat> <port start= '1024' end= '65535' /> </nat> </forward> <bridge name= 'virbr101' stp= 'on' delay= '0' /> <ip address= '10.55.44.1' netmask= '255.255.255.0' > <dhcp> <range start= '10.55.44.2' end= '10.55.44.254' /> </dhcp> </ip> </network> Define The Network With the above xml file: virsh net-define foonet.xml The network definition can now be found in /etc/libvirt/qemu/networks/foonet.xml <!-- /etc/libvirt/qemu/networks/foonet.xml --> <!-- WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE OVERWRITTEN AND LOST. Changes to this xml configuration should be made using: virsh net-edit foonet or other application using the libvirt API. --> <network> <name> foonet </name> <uuid> e6e40bfc-d449-4043-924c-ca0f0edf4210 </uuid> <forward mode= 'nat' > <nat> <port start= '1024' end= '65535' /> </nat> </forward> <bridge name= 'virbr111' stp= 'on' delay= '0' /> <mac address= '52:54:00:49:a7:f8' /> <ip address= '10.55.44.1' netmask= '255.255.255.0' > <dhcp> <range start= '10.55.44.2' end= '10.55.44.254' /> </dhcp> </ip> </network> You could also start the network without defining it using virsh net-create foonet.xml . Start/Stop Start the network virsh net-start foonet Stop the network virsh net-destroy foonet Undefine the network virsh net-undefine foonet Autostart the network virsh net-autostart foonet Disable autostart for the network virsh net-autostart foonet --disable Tab completion is you friend!","title":"Add KVM Network With Virsh"},{"location":"posts/add-kvm-network-with-virsh/#introduction","text":"This is a short and sweet walk-through for how to create a new network for libvirt for kvm , from the command line, using virsh .","title":"Introduction"},{"location":"posts/add-kvm-network-with-virsh/#name-resolution","text":"Let's start with name resolution. Install libnss-libvirt : apt install libnss-libvirt In /etc/nsswitch.conf , add libvirt to hosts key. # /etc/nsswitch.conf # change this ... hosts: files dns mymachines ... # to this ... hosts: files libvirt dns mymachines ...","title":"Name Resolution"},{"location":"posts/add-kvm-network-with-virsh/#starter-xml","text":"You could dumpxml on the existing default network: virsh net-dumpxml default > foonet.xml Then, edit foonet.xml: remove the network uuid change the network name to taste remove the bridge mac change the bridge name to taste change the bridge ip address and dhcp range to taste <!-- foonet.xml --> <network> <name> foonet </name> <forward mode= 'nat' > <nat> <port start= '1024' end= '65535' /> </nat> </forward> <bridge name= 'virbr101' stp= 'on' delay= '0' /> <ip address= '10.55.44.1' netmask= '255.255.255.0' > <dhcp> <range start= '10.55.44.2' end= '10.55.44.254' /> </dhcp> </ip> </network>","title":"Starter XML"},{"location":"posts/add-kvm-network-with-virsh/#define-the-network","text":"With the above xml file: virsh net-define foonet.xml The network definition can now be found in /etc/libvirt/qemu/networks/foonet.xml <!-- /etc/libvirt/qemu/networks/foonet.xml --> <!-- WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE OVERWRITTEN AND LOST. Changes to this xml configuration should be made using: virsh net-edit foonet or other application using the libvirt API. --> <network> <name> foonet </name> <uuid> e6e40bfc-d449-4043-924c-ca0f0edf4210 </uuid> <forward mode= 'nat' > <nat> <port start= '1024' end= '65535' /> </nat> </forward> <bridge name= 'virbr111' stp= 'on' delay= '0' /> <mac address= '52:54:00:49:a7:f8' /> <ip address= '10.55.44.1' netmask= '255.255.255.0' > <dhcp> <range start= '10.55.44.2' end= '10.55.44.254' /> </dhcp> </ip> </network> You could also start the network without defining it using virsh net-create foonet.xml .","title":"Define The Network"},{"location":"posts/add-kvm-network-with-virsh/#startstop","text":"Start the network virsh net-start foonet Stop the network virsh net-destroy foonet Undefine the network virsh net-undefine foonet Autostart the network virsh net-autostart foonet Disable autostart for the network virsh net-autostart foonet --disable Tab completion is you friend!","title":"Start/Stop"},{"location":"posts/ansible-kvm-router-lab-part-1/","text":"date: 2021-10-16 Introduction This is a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. This achieves the ability to quickly set up a router lab for the purposes of experimenting with iptables, or whatever else you want to use for routing or firewalls. This is also, for myself, an opportunity to learn ansible. In Ansible KVM Router Lab Part 2 , I break down the script build_vms.bash . In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab. In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab. Networking I begin by setting up a new network in libvirt, which will serve as an out-of-band network for connecting to the lab virtual machines. This is covered in a previous blog post . Overview The lab consists of seven virtual machines. I begin by creating a base Debian 11 virtual machine called dnet by connecting to my physical server using virt-manager . After creating a base virtual machine, the next step is to create a clone from which to work. I call this machine dcon . The client clones consist of 5 virtual machines named dnetone through dnetfive . Once set up, all five virtual machines are reachable through the out-of-band network. But there are also two bridge networks connecting the client clones to each other. The first and second clones are connected to each other on the upper bridge network, with the first clone acting as a router for the second. The second, third, fourth, and fifth clones are connected to each other on the lower bridge network, with the second clone acting as a router for the third, fourth, and fifth clones. Traffic from the second clone will go through the first clone to reach the internet, and traffic from the third, fourth, and fifth clones will go through the second clone and then through the first clone to reach the internet. DHCP is handled by dnsmasq on the first clone and the second clone. Resources For ansible I used the ansible documentation . This blog post by Brian Linkletter is also really helpful. Control Node Setup Create a control node by cloning the base virtual machine. virt-clone --original dnet --name dcon --auto-clone Configure ansible host file # ~/.ansible.cfg [defaults] inventory = ~/router-lab/ansible/hosts.yml Setup bashrc # ~/.bashrc export LIBVIRT_DEFAULT_URI = \"qemu+ssh://<user>@<server>/system\" alias ansible-pb = anspb anspb () { ANS_DIR = ~/router-lab/ansible/playbooks ; echo Changing to \" ${ ANS_DIR } \" and executing: ansible-playbook \" ${ @ } \" ( cd $ANS_DIR || exit ; ansible-playbook \" ${ @ } \" ) } configure Vim or similar for editing bash and python install apps apt install ansible ansible-lint libvirt-clients apt install --no-install-recommends virtinst The control node needs root ssh access to the base virtual machine so that it will have root ssh access to the clones. To Be Continued In the next blog post, Ansible KVM Router Lab Part 2 , I begin breaking down the bash scripts which build out the lab, beginning with build_vms.bash .","title":"Ansible KVM Router Lab Part 1"},{"location":"posts/ansible-kvm-router-lab-part-1/#introduction","text":"This is a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. This achieves the ability to quickly set up a router lab for the purposes of experimenting with iptables, or whatever else you want to use for routing or firewalls. This is also, for myself, an opportunity to learn ansible. In Ansible KVM Router Lab Part 2 , I break down the script build_vms.bash . In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab. In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab.","title":"Introduction"},{"location":"posts/ansible-kvm-router-lab-part-1/#networking","text":"I begin by setting up a new network in libvirt, which will serve as an out-of-band network for connecting to the lab virtual machines. This is covered in a previous blog post .","title":"Networking"},{"location":"posts/ansible-kvm-router-lab-part-1/#overview","text":"The lab consists of seven virtual machines. I begin by creating a base Debian 11 virtual machine called dnet by connecting to my physical server using virt-manager . After creating a base virtual machine, the next step is to create a clone from which to work. I call this machine dcon . The client clones consist of 5 virtual machines named dnetone through dnetfive . Once set up, all five virtual machines are reachable through the out-of-band network. But there are also two bridge networks connecting the client clones to each other. The first and second clones are connected to each other on the upper bridge network, with the first clone acting as a router for the second. The second, third, fourth, and fifth clones are connected to each other on the lower bridge network, with the second clone acting as a router for the third, fourth, and fifth clones. Traffic from the second clone will go through the first clone to reach the internet, and traffic from the third, fourth, and fifth clones will go through the second clone and then through the first clone to reach the internet. DHCP is handled by dnsmasq on the first clone and the second clone.","title":"Overview"},{"location":"posts/ansible-kvm-router-lab-part-1/#resources","text":"For ansible I used the ansible documentation . This blog post by Brian Linkletter is also really helpful.","title":"Resources"},{"location":"posts/ansible-kvm-router-lab-part-1/#control-node-setup","text":"Create a control node by cloning the base virtual machine. virt-clone --original dnet --name dcon --auto-clone Configure ansible host file # ~/.ansible.cfg [defaults] inventory = ~/router-lab/ansible/hosts.yml Setup bashrc # ~/.bashrc export LIBVIRT_DEFAULT_URI = \"qemu+ssh://<user>@<server>/system\" alias ansible-pb = anspb anspb () { ANS_DIR = ~/router-lab/ansible/playbooks ; echo Changing to \" ${ ANS_DIR } \" and executing: ansible-playbook \" ${ @ } \" ( cd $ANS_DIR || exit ; ansible-playbook \" ${ @ } \" ) } configure Vim or similar for editing bash and python install apps apt install ansible ansible-lint libvirt-clients apt install --no-install-recommends virtinst The control node needs root ssh access to the base virtual machine so that it will have root ssh access to the clones.","title":"Control Node Setup"},{"location":"posts/ansible-kvm-router-lab-part-1/#to-be-continued","text":"In the next blog post, Ansible KVM Router Lab Part 2 , I begin breaking down the bash scripts which build out the lab, beginning with build_vms.bash .","title":"To Be Continued"},{"location":"posts/ansible-kvm-router-lab-part-2/","text":"date: 2021-10-16 Introduction This is Part 2 of a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. Ansible KVM Router Lab Part 1 is an overview. In this post I begin breaking down the bash scripts which build the router lab, beginning with build_vms.bash . In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab. In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab. build_vms.bash check_uid \"0\" build_vms.bash begins by making sure that it is run as the root user. This is because root is required to ssh into the clones to change their hostnames, machine-ids, and host-ssh-keys. You can call with sudo bash build_vms.bash . For this same reason, ~/.ssh/known_hosts is useless so it is deleted (and then rebuilt). function build_vms() Next, build_vms.bash calls build_vms , which loops over the array of MACHINES, which is an array that holds that names of the lab clients, passing each name in turn to create_vm . create_vm creates the virtual machine if it does not already exist, using virt-clone , and then calls start_vm to start it. start_vm is exported from env.bash , and per parsing the output of virsh list --inactive , starts the virtual machine if it is not running. function set_hostnames() Next, build_vms.bash calls set_hostnames , which simultaneously calls set_hostname on the entire MACHINES array. set_hostname in turn waits for the virtual machine to be fully booted, then updates the files /etc/hostname and /etc/hosts , and then reboots the virtual machine to apply the new hostname. function confirm_hostnames() confirm_hostnames simultaneously calls confirm_hostname against the entire MACHINES array. confirm_hostname waits for the virtual machine to be fully booted, then confirms the correct hostname in /etc/hostname . function confirm_hostnames_in_hosts() confirm_hostnames_in_hosts works almost exactly the same as confirm_hostnames , but this time the file /etc/hosts on the virtual machine is grepped for the proper hostname , and corrected if necessary. function reset_hosts_ssh_keys() reset_hosts_ssh_keys simultaneously calls reset_host_ssh_keys against the MACHINES array, which in turn compares the host_ssh_key of the virtual machine against the bas3 virtual machine, and if necessary deletes /etc/ssh/ssh_host_* , generates new host_ssh_keys, restarts sshd on the virtual machine, removes ~/.ssh/known_hosts , and then reruns itself in order to confirm the new host_ssh_keys. function reset_machine_ids() reset_machine_ids simultaneously calls reset_machine_id against the entire MACHINES array, which in turn checks the machine-id of the virtual machine to make sure that it is different than the machine-id of the base virtual machine, and if necessary deletes /etc/machine-id and /var/lib/dbus/machine-id and recreates them. To Be Continued In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab.","title":"Ansible KVM Router Lab Part 2"},{"location":"posts/ansible-kvm-router-lab-part-2/#introduction","text":"This is Part 2 of a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. Ansible KVM Router Lab Part 1 is an overview. In this post I begin breaking down the bash scripts which build the router lab, beginning with build_vms.bash . In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab. In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab.","title":"Introduction"},{"location":"posts/ansible-kvm-router-lab-part-2/#build_vmsbash","text":"","title":"build_vms.bash"},{"location":"posts/ansible-kvm-router-lab-part-2/#check_uid-0","text":"build_vms.bash begins by making sure that it is run as the root user. This is because root is required to ssh into the clones to change their hostnames, machine-ids, and host-ssh-keys. You can call with sudo bash build_vms.bash . For this same reason, ~/.ssh/known_hosts is useless so it is deleted (and then rebuilt).","title":"check_uid \"0\""},{"location":"posts/ansible-kvm-router-lab-part-2/#function-build_vms","text":"Next, build_vms.bash calls build_vms , which loops over the array of MACHINES, which is an array that holds that names of the lab clients, passing each name in turn to create_vm . create_vm creates the virtual machine if it does not already exist, using virt-clone , and then calls start_vm to start it. start_vm is exported from env.bash , and per parsing the output of virsh list --inactive , starts the virtual machine if it is not running.","title":"function build_vms()"},{"location":"posts/ansible-kvm-router-lab-part-2/#function-set_hostnames","text":"Next, build_vms.bash calls set_hostnames , which simultaneously calls set_hostname on the entire MACHINES array. set_hostname in turn waits for the virtual machine to be fully booted, then updates the files /etc/hostname and /etc/hosts , and then reboots the virtual machine to apply the new hostname.","title":"function set_hostnames()"},{"location":"posts/ansible-kvm-router-lab-part-2/#function-confirm_hostnames","text":"confirm_hostnames simultaneously calls confirm_hostname against the entire MACHINES array. confirm_hostname waits for the virtual machine to be fully booted, then confirms the correct hostname in /etc/hostname .","title":"function confirm_hostnames()"},{"location":"posts/ansible-kvm-router-lab-part-2/#function-confirm_hostnames_in_hosts","text":"confirm_hostnames_in_hosts works almost exactly the same as confirm_hostnames , but this time the file /etc/hosts on the virtual machine is grepped for the proper hostname , and corrected if necessary.","title":"function confirm_hostnames_in_hosts()"},{"location":"posts/ansible-kvm-router-lab-part-2/#function-reset_hosts_ssh_keys","text":"reset_hosts_ssh_keys simultaneously calls reset_host_ssh_keys against the MACHINES array, which in turn compares the host_ssh_key of the virtual machine against the bas3 virtual machine, and if necessary deletes /etc/ssh/ssh_host_* , generates new host_ssh_keys, restarts sshd on the virtual machine, removes ~/.ssh/known_hosts , and then reruns itself in order to confirm the new host_ssh_keys.","title":"function reset_hosts_ssh_keys()"},{"location":"posts/ansible-kvm-router-lab-part-2/#function-reset_machine_ids","text":"reset_machine_ids simultaneously calls reset_machine_id against the entire MACHINES array, which in turn checks the machine-id of the virtual machine to make sure that it is different than the machine-id of the base virtual machine, and if necessary deletes /etc/machine-id and /var/lib/dbus/machine-id and recreates them.","title":"function reset_machine_ids()"},{"location":"posts/ansible-kvm-router-lab-part-2/#to-be-continued","text":"In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab.","title":"To Be Continued"},{"location":"posts/ansible-kvm-router-lab-part-3/","text":"date: 2021-10-16 Introduction This is Part 3 of a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. Ansible KVM Router Lab Part 1 is an overview. In Ansible KVM Router Lab Part 2 , I break down the script build_vms.bash . In this post I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab. In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab. define_bridge_networks.bash The router-lab has two bridge networks, in addition to the initial out-of-band network which is used to contact the virtual machines directly. check_uid \"${USER_UID}\" define_bridge_networks.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash define_bridge_networks.bash . function define_bridge_networks() define_bridge_networks calls define_bridge_network twice, once for each of the upper bridge and the lower bridge. define_bridge_network parses the output of virsh net-list --all to determine if the network is defined yet. If not, virsh net-define vm_router_lab_lower_bridge.xml or virsh net-define vm_router_lab_upper_bridge.xml are invoked as necessary. define_bridge_network then recursively calls itself for confirmation. Links for vm_router_lab_upper_bridge.xml and vm_router_lab_lower_bridge.xml . function start_bridge_networks() start_bridge_networks calls start_bridge_network twice, once for each of the upper and the lower bridge. start_bridge_network in turn parses the output of virsh net-info vm_router_lab_upper_bridge and/or virsh net-info vm_router_lab_lower_bridge to determine if the cooresponding network is running, and if not invokes virsh net-start vm_router_lab_upper_bridge or virsh net-start vm_router_lab_lower_bridge , and then recursively calls itself again for confirmation. function autostart_bridge_networks() autostart_bridge_networks is nearly identical to start_bridge_networks , but virsh net-autostart vm_router_lab_upper_bridge or virsh net-autostart vm_router_lab_lower_bridge , are invoked in order to mark the cooresponding network to autostart. shutdown_vms.bash After creating the upper and lower bridge networks, it is necessary to shut down the lab clients before connecting the lab clients to the bridge networks. This is because network interfaces must be permanently added to the lab client definitions. check_uid \"${USER_UID}\" shutdown_vms.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash shutdown_vms.bash . function shutdown_vms() shutdown_vms simultaneously calls shutdown_vm on the entire MACHINES array. shutdown_vm in turn parses the output of virsh list --state-running to determine if the virtual machine is running, and if so invokes virsh shutdown <vm> . shutdown_vm then recursively calls itself to confirm that the virtual machine is indeed shut down. To Be Continued In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab.","title":"Ansible KVM Router Lab Part 3"},{"location":"posts/ansible-kvm-router-lab-part-3/#introduction","text":"This is Part 3 of a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. Ansible KVM Router Lab Part 1 is an overview. In Ansible KVM Router Lab Part 2 , I break down the script build_vms.bash . In this post I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab. In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab.","title":"Introduction"},{"location":"posts/ansible-kvm-router-lab-part-3/#define_bridge_networksbash","text":"The router-lab has two bridge networks, in addition to the initial out-of-band network which is used to contact the virtual machines directly.","title":"define_bridge_networks.bash"},{"location":"posts/ansible-kvm-router-lab-part-3/#check_uid-user_uid","text":"define_bridge_networks.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash define_bridge_networks.bash .","title":"check_uid \"${USER_UID}\""},{"location":"posts/ansible-kvm-router-lab-part-3/#function-define_bridge_networks","text":"define_bridge_networks calls define_bridge_network twice, once for each of the upper bridge and the lower bridge. define_bridge_network parses the output of virsh net-list --all to determine if the network is defined yet. If not, virsh net-define vm_router_lab_lower_bridge.xml or virsh net-define vm_router_lab_upper_bridge.xml are invoked as necessary. define_bridge_network then recursively calls itself for confirmation. Links for vm_router_lab_upper_bridge.xml and vm_router_lab_lower_bridge.xml .","title":"function define_bridge_networks()"},{"location":"posts/ansible-kvm-router-lab-part-3/#function-start_bridge_networks","text":"start_bridge_networks calls start_bridge_network twice, once for each of the upper and the lower bridge. start_bridge_network in turn parses the output of virsh net-info vm_router_lab_upper_bridge and/or virsh net-info vm_router_lab_lower_bridge to determine if the cooresponding network is running, and if not invokes virsh net-start vm_router_lab_upper_bridge or virsh net-start vm_router_lab_lower_bridge , and then recursively calls itself again for confirmation.","title":"function start_bridge_networks()"},{"location":"posts/ansible-kvm-router-lab-part-3/#function-autostart_bridge_networks","text":"autostart_bridge_networks is nearly identical to start_bridge_networks , but virsh net-autostart vm_router_lab_upper_bridge or virsh net-autostart vm_router_lab_lower_bridge , are invoked in order to mark the cooresponding network to autostart.","title":"function autostart_bridge_networks()"},{"location":"posts/ansible-kvm-router-lab-part-3/#shutdown_vmsbash","text":"After creating the upper and lower bridge networks, it is necessary to shut down the lab clients before connecting the lab clients to the bridge networks. This is because network interfaces must be permanently added to the lab client definitions.","title":"shutdown_vms.bash"},{"location":"posts/ansible-kvm-router-lab-part-3/#check_uid-user_uid_1","text":"shutdown_vms.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash shutdown_vms.bash .","title":"check_uid \"${USER_UID}\""},{"location":"posts/ansible-kvm-router-lab-part-3/#function-shutdown_vms","text":"shutdown_vms simultaneously calls shutdown_vm on the entire MACHINES array. shutdown_vm in turn parses the output of virsh list --state-running to determine if the virtual machine is running, and if so invokes virsh shutdown <vm> . shutdown_vm then recursively calls itself to confirm that the virtual machine is indeed shut down.","title":"function shutdown_vms()"},{"location":"posts/ansible-kvm-router-lab-part-3/#to-be-continued","text":"In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab.","title":"To Be Continued"},{"location":"posts/ansible-kvm-router-lab-part-4/","text":"date: 2021-10-17 Introduction This is Part 4 of a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. Ansible KVM Router Lab Part 1 is an overview. In Ansible KVM Router Lab Part 2 , I break down the script build_vms.bash . In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In this post I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab. In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab. connect_vms_to_bridges.bash Aside from the out-of-band network which can be used to contact the lab clients directly, the lab clients are connected to each other using two bridge networks. As explained in Ansible KVM Router Lab Part 1 , lab clients one and two are connected to the upper bridge, and lab clients two, three, four, and five are connected to the lower bridge with the first client acting as a router for the second client, and the second client acting as a client for the third, fourth, and fifth clients. check_uid \"${USER_UID}\" connect_vms_to_bridges.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash connect_vms_to_bridges.bash . function connect_upper_bridge() connect_upper_bridge calls connect_vm_to_bridge against the first lab client and the upper bridge, and again against the second lab client and the upper bridge. function connect_lower_bridge() connect_lower_bridge calls connect_vm_to_bridge against the second lab client and the lower bridge, against the third lab client and the lower bridge, against the fourth lab client and the lower bridge, and against the fifth lab client and the lower bridge. function connect_vm_to_bridge() connect_vm_to_bridge parses the output of virsh dominfo against the intended lab client to verify that it is shutdown. Then, if the intended lab client is shutdown, connect_vm_to_bridge parses the output of virsh domiflist to find out if the intended new interface is yet defined, and if not invokes virsh attach-interface <vm> --type network --source <bridge network> . Finally, connect_vm_to_bridge recursively calls itself for verification. start_vms.bash After defining the new network interfaces for all the lab clients, you can boot them. check_uid \"${USER_UID}\" start_vms.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash start_vms.bash . function start_vms() start_vms calls start_vm against the entire MACHINES array, simultaneously. start_vm is exported from env.bash , and per parsing the output of virsh list --inactive , starts the virtual machine if it is not running. rebuild_known_hosts.bash You will need to have a valid list of known_hosts in order for ansible to connect to the lab clients. The script deletes ~/.ssh/known_hosts and then initiates an ssh connection to all the router lab clients in order to repopulate ~/.ssh/known_hosts . check_uid \"${USER_UID}\" rebuild_known_hosts.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash rebuild_known_hosts . To Be Continued In the next blog post, Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab.","title":"Ansible KVM Router Lab Part 4"},{"location":"posts/ansible-kvm-router-lab-part-4/#introduction","text":"This is Part 4 of a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. Ansible KVM Router Lab Part 1 is an overview. In Ansible KVM Router Lab Part 2 , I break down the script build_vms.bash . In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In this post I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab. In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab.","title":"Introduction"},{"location":"posts/ansible-kvm-router-lab-part-4/#connect_vms_to_bridgesbash","text":"Aside from the out-of-band network which can be used to contact the lab clients directly, the lab clients are connected to each other using two bridge networks. As explained in Ansible KVM Router Lab Part 1 , lab clients one and two are connected to the upper bridge, and lab clients two, three, four, and five are connected to the lower bridge with the first client acting as a router for the second client, and the second client acting as a client for the third, fourth, and fifth clients.","title":"connect_vms_to_bridges.bash"},{"location":"posts/ansible-kvm-router-lab-part-4/#check_uid-user_uid","text":"connect_vms_to_bridges.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash connect_vms_to_bridges.bash .","title":"check_uid \"${USER_UID}\""},{"location":"posts/ansible-kvm-router-lab-part-4/#function-connect_upper_bridge","text":"connect_upper_bridge calls connect_vm_to_bridge against the first lab client and the upper bridge, and again against the second lab client and the upper bridge.","title":"function connect_upper_bridge()"},{"location":"posts/ansible-kvm-router-lab-part-4/#function-connect_lower_bridge","text":"connect_lower_bridge calls connect_vm_to_bridge against the second lab client and the lower bridge, against the third lab client and the lower bridge, against the fourth lab client and the lower bridge, and against the fifth lab client and the lower bridge.","title":"function connect_lower_bridge()"},{"location":"posts/ansible-kvm-router-lab-part-4/#function-connect_vm_to_bridge","text":"connect_vm_to_bridge parses the output of virsh dominfo against the intended lab client to verify that it is shutdown. Then, if the intended lab client is shutdown, connect_vm_to_bridge parses the output of virsh domiflist to find out if the intended new interface is yet defined, and if not invokes virsh attach-interface <vm> --type network --source <bridge network> . Finally, connect_vm_to_bridge recursively calls itself for verification.","title":"function connect_vm_to_bridge()"},{"location":"posts/ansible-kvm-router-lab-part-4/#start_vmsbash","text":"After defining the new network interfaces for all the lab clients, you can boot them.","title":"start_vms.bash"},{"location":"posts/ansible-kvm-router-lab-part-4/#check_uid-user_uid_1","text":"start_vms.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash start_vms.bash .","title":"check_uid \"${USER_UID}\""},{"location":"posts/ansible-kvm-router-lab-part-4/#function-start_vms","text":"start_vms calls start_vm against the entire MACHINES array, simultaneously. start_vm is exported from env.bash , and per parsing the output of virsh list --inactive , starts the virtual machine if it is not running.","title":"function start_vms()"},{"location":"posts/ansible-kvm-router-lab-part-4/#rebuild_known_hostsbash","text":"You will need to have a valid list of known_hosts in order for ansible to connect to the lab clients. The script deletes ~/.ssh/known_hosts and then initiates an ssh connection to all the router lab clients in order to repopulate ~/.ssh/known_hosts .","title":"rebuild_known_hosts.bash"},{"location":"posts/ansible-kvm-router-lab-part-4/#check_uid-user_uid_2","text":"rebuild_known_hosts.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash rebuild_known_hosts .","title":"check_uid \"${USER_UID}\""},{"location":"posts/ansible-kvm-router-lab-part-4/#to-be-continued","text":"In the next blog post, Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab.","title":"To Be Continued"},{"location":"posts/ansible-kvm-router-lab-part-5/","text":"date: 2021-10-17 Introduction This is Part 5 of a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. Ansible KVM Router Lab Part 1 is an overview. In Ansible KVM Router Lab Part 2 , I break down the script build_vms.bash . In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In this post I explain how I use Ansible to finish constructing the lab. In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab. Setup Ansible Configure ansible host file # ~/.ansible.cfg [defaults] inventory = ~/router-lab/ansible/hosts.yml Setup bashrc # ~/.bashrc export LIBVIRT_DEFAULT_URI = \"qemu+ssh://<user>@<server>/system\" alias ansible-pb = anspb anspb () { ANS_DIR = ~/router-lab/ansible/playbooks ; echo Changing to \" ${ ANS_DIR } \" and executing: ansible-playbook \" ${ @ } \" ( cd $ANS_DIR || exit ; ansible-playbook \" ${ @ } \" ) } install apps apt install ansible ansible-lint Run Ansible ansible-pb build_out_routers.yml -K or if you want to first update all the clients ansible-pb update_and_build.yml -K Ansible Tasks This is an explaination of the tasks in the Ansible Playbook. Playbooks are executed from top to bottom. Install dnsmasq , iptables-persistent This task is only run against the first and second lab clients as they are the routers. Install traceroute Traceroute is parsed in a later task to confirm that traffic is following the correct route. (Also incidentally installs needrestart and screen .) Backup /etc/network/interfaces This is a simple bash command that tests if /etc/network/interfaces.bak exists, and if not creates it. Update Network Config This task updates /etc/network/interfaces in all the lab clients to describe the network interfaces needed to connect to each other. For instance, here is the new /etc/network/interfaces file for dnettwo . # /etc/network/interfaces # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). source /etc/network/interfaces.d/* # The loopback network interface auto lo iface lo inet loopback # The primary network interface allow-hotplug enp1s0 iface enp1s0 inet dhcp # The primary network interface allow-hotplug enp7s0 iface enp7s0 inet dhcp auto enp8s0 iface enp8s0 inet static address 10.4.4.1 network 10.4.4.0 netmask 255.255.255.0 broadcast 10.4.4.255 Backup /etc/dnsmasq.conf This is a simple bash command that tests if /etc/dnsmasq.conf.bak exists, and if not creates it. (only applies to the two router clients) Configure dnsmasq This task copies the templates for /etc/dnsmasq.conf to each of the two router clients. dnsmasq is used to provide DHCP (and name resolution). For instance, here is the new /etc/dnsmasq.conf for dnetone . # /etc/dnsmasq.conf dhcp-range = 10.5.5.50,10.5.5.150 listen-address = 127.0.0.1, 10.5.5.1 Configure Network ifup This applies to all the lab clients except for the first one, changes the default route. A bash script is copied from template to /etc/network/if-up.d/ifup-script . For instance here is ifup-script for dnetthree . #!/bin/bash # /etc/network/if-up.d/ifup-script default_dev = \" $( ip route | head -1 | awk '{print $5}' ) \" echo \" ${ default_dev } \" if [ \" ${ default_dev } \" == \"enp1s0\" ] then ip route del default via 10 .55.44.1 dev enp1s0 fi if [ \" ${ default_dev } \" ! = \"enp7s0\" ] then ip route add default via 10 .4.4.1 dev enp7s0 fi Restart Network and dnsmasq This is sequential: enp7s0 is restarted on dnetone dnsmasq is restarted on dnetone , offering service on enp7s0 enp7s0 and enp8s0 are restarted on dnettwo , thus soliciting dhcp service on enp7s0 , and triggering /etc/network/if-up.d/ifup-script dnsmasq is restarted on dnettwo , offering service on enp8s0 enp7s0 is restarted on dnetthree , dnetfour , and dnetfive , thus soliciting dhcp service on enp7s0 , and triggering /etc/network/if-up.d/ifup-script Backup /etc/sysctl.conf This is a simple bash command that tests if /etc/sysctl.conf.bak exists, and if not creates it. (only applies to the two router clients) Enable ipv4 forwarding This is a simple bash command that uncomments the option for ipv4 forwarding in /etc/sysctl.conf , applies only to the two routers. # /etc/sysctl.conf ... # this #net.ipv4.ip_forward=1 ... # becomes this net.ipv4.ip_forward = 1 ... Start ipv4 forwarding This simple bash command starts ipv4 forwarding , applies only to the two routers. bash -c \"sysctl -w net.ipv4.ip_forward=1\" Configure iptables workaround This applies only to the two router clients. From iptables 's point of view, the ansible connection isn't a RELATED INPUT connection, thus it is necessary to bring up a firewall in a two-step process that involves first ACCEPTING RELATED OUTPUT connections in a workaround. From ansible template, the following is copied to /dev/shm/iptables_workaround # /dev/shm/iptables_workaround *filter :INPUT ACCEPT [0:0] :OUTPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] -A INPUT -j ACCEPT -m conntrack --ctstate ESTABLISHED,RELATED -A OUTPUT -j ACCEPT -m conntrack --ctstate ESTABLISHED,RELATED COMMIT Apply iptables workaround This applies only to the two router clients. The following command is dispatched to apply the above iptables_workaround : bash -c \"iptables-restore < /dev/shm/iptables_workaround\" Configure iptables This applies only to the two router clients. From ansible template the following is copied to /etc/iptables/rules.v4 on dnetone . *nat -A POSTROUTING -o enp1s0 -j MASQUERADE COMMIT *filter -A INPUT -i lo -j ACCEPT # allow ssh, so that we do not lock ourselves -A INPUT -i enp1s0 -p tcp -m tcp --dport 22 -j ACCEPT # allow incoming traffic to the outgoing connections, # et al for clients from the private network -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT # prohibit everything else incoming -A INPUT -i enp1s0 -j DROP COMMIT From ansible template the following is copied to /etc/iptables/rules.v4 on dnettwo . *nat -A POSTROUTING -o enp7s0 -j MASQUERADE COMMIT *filter -A INPUT -i lo -j ACCEPT # allow ssh, so that we do not lock ourselves -A INPUT -i enp7s0 -p tcp -m tcp --dport 22 -j ACCEPT # allow incoming traffic to the outgoing connections, # et al for clients from the private network -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT # prohibit everything else incoming -A INPUT -i enp7s0 -j DROP COMMIT Apply iptables firewall This applies only to the two router clients. The following command is dispatched to apply the above from /etc/iptables/rules.v4 : bash -c \"iptables-restore < /etc/iptables/rules.v4\" traceroute test The following script is dispatched to dnettwo : #!/bin/bash RESULT = \" $( traceroute 8 .8.8.8 ) \" FIRST_HOP = \" $( echo \" ${ RESULT } \" | head -2 | tail -1 | awk '{print $2}' ) \" if [ \" ${ FIRST_HOP } \" == \"10.5.5.1\" ] then exit 0 else exit 1 fi The following script is dispatched to dnetthree , dnetfour , and dnetfive : #!/bin/bash RESULT = \" $( traceroute 8 .8.8.8 ) \" FIRST_HOP = \" $( echo \" ${ RESULT } \" | head -2 | tail -1 | awk '{print $2}' ) \" if [ \" ${ FIRST_HOP } \" ! = \"10.4.4.1\" ] then exit 1 fi SECOND_HOP = \" $( echo \" ${ RESULT } \" | head -3 | tail -1 | awk '{print $2}' ) \" if [ \" ${ SECOND_HOP } \" == \"10.5.5.1\" ] then exit 0 else exit 1 fi To Be Continued In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab.","title":"Ansible KVM Router Lab Part 5"},{"location":"posts/ansible-kvm-router-lab-part-5/#introduction","text":"This is Part 5 of a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. Ansible KVM Router Lab Part 1 is an overview. In Ansible KVM Router Lab Part 2 , I break down the script build_vms.bash . In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In this post I explain how I use Ansible to finish constructing the lab. In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab.","title":"Introduction"},{"location":"posts/ansible-kvm-router-lab-part-5/#setup-ansible","text":"Configure ansible host file # ~/.ansible.cfg [defaults] inventory = ~/router-lab/ansible/hosts.yml Setup bashrc # ~/.bashrc export LIBVIRT_DEFAULT_URI = \"qemu+ssh://<user>@<server>/system\" alias ansible-pb = anspb anspb () { ANS_DIR = ~/router-lab/ansible/playbooks ; echo Changing to \" ${ ANS_DIR } \" and executing: ansible-playbook \" ${ @ } \" ( cd $ANS_DIR || exit ; ansible-playbook \" ${ @ } \" ) } install apps apt install ansible ansible-lint","title":"Setup Ansible"},{"location":"posts/ansible-kvm-router-lab-part-5/#run-ansible","text":"ansible-pb build_out_routers.yml -K or if you want to first update all the clients ansible-pb update_and_build.yml -K","title":"Run Ansible"},{"location":"posts/ansible-kvm-router-lab-part-5/#ansible-tasks","text":"This is an explaination of the tasks in the Ansible Playbook. Playbooks are executed from top to bottom.","title":"Ansible Tasks"},{"location":"posts/ansible-kvm-router-lab-part-5/#install-dnsmasq-iptables-persistent","text":"This task is only run against the first and second lab clients as they are the routers.","title":"Install dnsmasq, iptables-persistent"},{"location":"posts/ansible-kvm-router-lab-part-5/#install-traceroute","text":"Traceroute is parsed in a later task to confirm that traffic is following the correct route. (Also incidentally installs needrestart and screen .)","title":"Install traceroute"},{"location":"posts/ansible-kvm-router-lab-part-5/#backup-etcnetworkinterfaces","text":"This is a simple bash command that tests if /etc/network/interfaces.bak exists, and if not creates it.","title":"Backup /etc/network/interfaces"},{"location":"posts/ansible-kvm-router-lab-part-5/#update-network-config","text":"This task updates /etc/network/interfaces in all the lab clients to describe the network interfaces needed to connect to each other. For instance, here is the new /etc/network/interfaces file for dnettwo . # /etc/network/interfaces # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). source /etc/network/interfaces.d/* # The loopback network interface auto lo iface lo inet loopback # The primary network interface allow-hotplug enp1s0 iface enp1s0 inet dhcp # The primary network interface allow-hotplug enp7s0 iface enp7s0 inet dhcp auto enp8s0 iface enp8s0 inet static address 10.4.4.1 network 10.4.4.0 netmask 255.255.255.0 broadcast 10.4.4.255","title":"Update Network Config"},{"location":"posts/ansible-kvm-router-lab-part-5/#backup-etcdnsmasqconf","text":"This is a simple bash command that tests if /etc/dnsmasq.conf.bak exists, and if not creates it. (only applies to the two router clients)","title":"Backup /etc/dnsmasq.conf"},{"location":"posts/ansible-kvm-router-lab-part-5/#configure-dnsmasq","text":"This task copies the templates for /etc/dnsmasq.conf to each of the two router clients. dnsmasq is used to provide DHCP (and name resolution). For instance, here is the new /etc/dnsmasq.conf for dnetone . # /etc/dnsmasq.conf dhcp-range = 10.5.5.50,10.5.5.150 listen-address = 127.0.0.1, 10.5.5.1","title":"Configure dnsmasq"},{"location":"posts/ansible-kvm-router-lab-part-5/#configure-network-ifup","text":"This applies to all the lab clients except for the first one, changes the default route. A bash script is copied from template to /etc/network/if-up.d/ifup-script . For instance here is ifup-script for dnetthree . #!/bin/bash # /etc/network/if-up.d/ifup-script default_dev = \" $( ip route | head -1 | awk '{print $5}' ) \" echo \" ${ default_dev } \" if [ \" ${ default_dev } \" == \"enp1s0\" ] then ip route del default via 10 .55.44.1 dev enp1s0 fi if [ \" ${ default_dev } \" ! = \"enp7s0\" ] then ip route add default via 10 .4.4.1 dev enp7s0 fi","title":"Configure Network ifup"},{"location":"posts/ansible-kvm-router-lab-part-5/#restart-network-and-dnsmasq","text":"This is sequential: enp7s0 is restarted on dnetone dnsmasq is restarted on dnetone , offering service on enp7s0 enp7s0 and enp8s0 are restarted on dnettwo , thus soliciting dhcp service on enp7s0 , and triggering /etc/network/if-up.d/ifup-script dnsmasq is restarted on dnettwo , offering service on enp8s0 enp7s0 is restarted on dnetthree , dnetfour , and dnetfive , thus soliciting dhcp service on enp7s0 , and triggering /etc/network/if-up.d/ifup-script","title":"Restart Network and dnsmasq"},{"location":"posts/ansible-kvm-router-lab-part-5/#backup-etcsysctlconf","text":"This is a simple bash command that tests if /etc/sysctl.conf.bak exists, and if not creates it. (only applies to the two router clients)","title":"Backup /etc/sysctl.conf"},{"location":"posts/ansible-kvm-router-lab-part-5/#enable-ipv4-forwarding","text":"This is a simple bash command that uncomments the option for ipv4 forwarding in /etc/sysctl.conf , applies only to the two routers. # /etc/sysctl.conf ... # this #net.ipv4.ip_forward=1 ... # becomes this net.ipv4.ip_forward = 1 ...","title":"Enable ipv4 forwarding"},{"location":"posts/ansible-kvm-router-lab-part-5/#start-ipv4-forwarding","text":"This simple bash command starts ipv4 forwarding , applies only to the two routers. bash -c \"sysctl -w net.ipv4.ip_forward=1\"","title":"Start ipv4 forwarding"},{"location":"posts/ansible-kvm-router-lab-part-5/#configure-iptables-workaround","text":"This applies only to the two router clients. From iptables 's point of view, the ansible connection isn't a RELATED INPUT connection, thus it is necessary to bring up a firewall in a two-step process that involves first ACCEPTING RELATED OUTPUT connections in a workaround. From ansible template, the following is copied to /dev/shm/iptables_workaround # /dev/shm/iptables_workaround *filter :INPUT ACCEPT [0:0] :OUTPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] -A INPUT -j ACCEPT -m conntrack --ctstate ESTABLISHED,RELATED -A OUTPUT -j ACCEPT -m conntrack --ctstate ESTABLISHED,RELATED COMMIT","title":"Configure iptables workaround"},{"location":"posts/ansible-kvm-router-lab-part-5/#apply-iptables-workaround","text":"This applies only to the two router clients. The following command is dispatched to apply the above iptables_workaround : bash -c \"iptables-restore < /dev/shm/iptables_workaround\"","title":"Apply iptables workaround"},{"location":"posts/ansible-kvm-router-lab-part-5/#configure-iptables","text":"This applies only to the two router clients. From ansible template the following is copied to /etc/iptables/rules.v4 on dnetone . *nat -A POSTROUTING -o enp1s0 -j MASQUERADE COMMIT *filter -A INPUT -i lo -j ACCEPT # allow ssh, so that we do not lock ourselves -A INPUT -i enp1s0 -p tcp -m tcp --dport 22 -j ACCEPT # allow incoming traffic to the outgoing connections, # et al for clients from the private network -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT # prohibit everything else incoming -A INPUT -i enp1s0 -j DROP COMMIT From ansible template the following is copied to /etc/iptables/rules.v4 on dnettwo . *nat -A POSTROUTING -o enp7s0 -j MASQUERADE COMMIT *filter -A INPUT -i lo -j ACCEPT # allow ssh, so that we do not lock ourselves -A INPUT -i enp7s0 -p tcp -m tcp --dport 22 -j ACCEPT # allow incoming traffic to the outgoing connections, # et al for clients from the private network -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT # prohibit everything else incoming -A INPUT -i enp7s0 -j DROP COMMIT","title":"Configure iptables"},{"location":"posts/ansible-kvm-router-lab-part-5/#apply-iptables-firewall","text":"This applies only to the two router clients. The following command is dispatched to apply the above from /etc/iptables/rules.v4 : bash -c \"iptables-restore < /etc/iptables/rules.v4\"","title":"Apply iptables firewall"},{"location":"posts/ansible-kvm-router-lab-part-5/#traceroute-test","text":"The following script is dispatched to dnettwo : #!/bin/bash RESULT = \" $( traceroute 8 .8.8.8 ) \" FIRST_HOP = \" $( echo \" ${ RESULT } \" | head -2 | tail -1 | awk '{print $2}' ) \" if [ \" ${ FIRST_HOP } \" == \"10.5.5.1\" ] then exit 0 else exit 1 fi The following script is dispatched to dnetthree , dnetfour , and dnetfive : #!/bin/bash RESULT = \" $( traceroute 8 .8.8.8 ) \" FIRST_HOP = \" $( echo \" ${ RESULT } \" | head -2 | tail -1 | awk '{print $2}' ) \" if [ \" ${ FIRST_HOP } \" ! = \"10.4.4.1\" ] then exit 1 fi SECOND_HOP = \" $( echo \" ${ RESULT } \" | head -3 | tail -1 | awk '{print $2}' ) \" if [ \" ${ SECOND_HOP } \" == \"10.5.5.1\" ] then exit 0 else exit 1 fi","title":"traceroute test"},{"location":"posts/ansible-kvm-router-lab-part-5/#to-be-continued","text":"In Ansible KVM Router Lab Part 6 , I explain disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks which are used to destroy the lab.","title":"To Be Continued"},{"location":"posts/ansible-kvm-router-lab-part-6/","text":"date: 2021-10-17 Introduction This is Part 6 of a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. Ansible KVM Router Lab Part 1 is an overview. In Ansible KVM Router Lab Part 2 , I break down the script build_vms.bash . In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab. In this post I explain how I use disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks.bash , to destroy the lab. shutdown_vms.bash I explain shutdown_vms.bash in Ansible KVM Router Lab Part 3 . disconnect_vms_from_bridges.bash check_uid \"${USER_UID}\" disconnect_vms_from_bridges.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash disconnect_vms_from_bridges.bash . function detach_vms() detach_vms loops over the MACHINES array, passing each name twice to detach_vm , once for the upper bridge network, and again for the lower bridge network. function detach_vm() detach_vm invokes virsh dominfo against the given virtual machine, and parses the output to decide if the machine is running or not. If the given virtual machine is running, detach_vm calls detach_running_vm against the given virtual machine and the given network. If the given virtual machine is not running, detach_vm calls detach_shut_off_vm against the given virtual machine and against the given network. function detach_running_vm() detach_running_vm invokes virsh domiflist against the given virtual machine and greps that for the given network to decide whether or not the given virtual machine is attached to the given network. If the given virtual machine is attached to the given network, detach_running_vm once again similarly invokes virsh domiflist , but this time parsing the mac of the attached interface. detach_running_vm then invokes virsh detach-interface against parsed mac, and then recursively calls itself for the purpose of verification. function detach_shut_off_vm() detach_shut_off_vm is almost identical to detach_running_vm , but the options for the invocation of virsh detach-interface are adjusted to be appropriate for a virtual machine which is not running. undefine_and_remove_vms.bash check_uid \"${USER_UID}\" undefine_and_remove_vms.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash undefine_and_remove_vms.bash . function destroy_vms() destroy_vms simultaneously passes the entire MACHINES array to destroy_vm , which parses the output of virsh list --all to find out if the virtual machine exists, and if it does invokes the command virsh undefine with the --remove-all-storage option, against the virtual machine. remove_bridge_networks.bash check_uid \"${USER_UID}\" remove_bridge_networks.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash remove_bridge_networks.bash . function disable_autostart_bridge_networks() disable_autostart_bridge_networks passes each of the upper and lower bridge network names to disable_autostart_bridge_network , which parses the output of virsh net-info to find out if the network has autostart enabled, and if it is, invokes virsh net-autostart with the --disable option to disable autostart for the given network, and then recursively calls itself for the purpose of verification. function stop_bridge_networks() stop_bridge_networks passes each of the upper and lower bridge network names to stop_bridge_network , which parses the output of virsh net-info in order to find out if the given network is running, and it if is, invokes virsh net-destroy against the given network to stop it, and then recursively calls itself for the purpose of verification. function undefine_bridge_networks() undefine_bridge_networks passes each of the upper and lower bridge network names to undefine_bridge_network , which parses the output of virsh net-list --all in order to find out if the given network is defined, and if it is, invokes virsh net-undefine against the given network to undefine it, and then recursively calls itself for the purpose of verification.","title":"Ansible KVM Router Lab Part 6"},{"location":"posts/ansible-kvm-router-lab-part-6/#introduction","text":"This is Part 6 of a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible. Ansible KVM Router Lab Part 1 is an overview. In Ansible KVM Router Lab Part 2 , I break down the script build_vms.bash . In Ansible KVM Router Lab Part 3 , I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 4 , I explain connect_vms_to_bridges.bash , start_vms.bash , and rebuild_known_hosts.bash scripts which are used to construct the lab. In Ansible KVM Router Lab Part 5 , I explain the ansible playbook tasks used to finish building the lab. In this post I explain how I use disconnect_vms_from_bridges.bash , undefine_and_remove_vms.bash , and remove_bridge_networks.bash , to destroy the lab.","title":"Introduction"},{"location":"posts/ansible-kvm-router-lab-part-6/#shutdown_vmsbash","text":"I explain shutdown_vms.bash in Ansible KVM Router Lab Part 3 .","title":"shutdown_vms.bash"},{"location":"posts/ansible-kvm-router-lab-part-6/#disconnect_vms_from_bridgesbash","text":"","title":"disconnect_vms_from_bridges.bash"},{"location":"posts/ansible-kvm-router-lab-part-6/#check_uid-user_uid","text":"disconnect_vms_from_bridges.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash disconnect_vms_from_bridges.bash .","title":"check_uid \"${USER_UID}\""},{"location":"posts/ansible-kvm-router-lab-part-6/#function-detach_vms","text":"detach_vms loops over the MACHINES array, passing each name twice to detach_vm , once for the upper bridge network, and again for the lower bridge network.","title":"function detach_vms()"},{"location":"posts/ansible-kvm-router-lab-part-6/#function-detach_vm","text":"detach_vm invokes virsh dominfo against the given virtual machine, and parses the output to decide if the machine is running or not. If the given virtual machine is running, detach_vm calls detach_running_vm against the given virtual machine and the given network. If the given virtual machine is not running, detach_vm calls detach_shut_off_vm against the given virtual machine and against the given network.","title":"function detach_vm()"},{"location":"posts/ansible-kvm-router-lab-part-6/#function-detach_running_vm","text":"detach_running_vm invokes virsh domiflist against the given virtual machine and greps that for the given network to decide whether or not the given virtual machine is attached to the given network. If the given virtual machine is attached to the given network, detach_running_vm once again similarly invokes virsh domiflist , but this time parsing the mac of the attached interface. detach_running_vm then invokes virsh detach-interface against parsed mac, and then recursively calls itself for the purpose of verification.","title":"function detach_running_vm()"},{"location":"posts/ansible-kvm-router-lab-part-6/#function-detach_shut_off_vm","text":"detach_shut_off_vm is almost identical to detach_running_vm , but the options for the invocation of virsh detach-interface are adjusted to be appropriate for a virtual machine which is not running.","title":"function detach_shut_off_vm()"},{"location":"posts/ansible-kvm-router-lab-part-6/#undefine_and_remove_vmsbash","text":"","title":"undefine_and_remove_vms.bash"},{"location":"posts/ansible-kvm-router-lab-part-6/#check_uid-user_uid_1","text":"undefine_and_remove_vms.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash undefine_and_remove_vms.bash .","title":"check_uid \"${USER_UID}\""},{"location":"posts/ansible-kvm-router-lab-part-6/#function-destroy_vms","text":"destroy_vms simultaneously passes the entire MACHINES array to destroy_vm , which parses the output of virsh list --all to find out if the virtual machine exists, and if it does invokes the command virsh undefine with the --remove-all-storage option, against the virtual machine.","title":"function destroy_vms()"},{"location":"posts/ansible-kvm-router-lab-part-6/#remove_bridge_networksbash","text":"","title":"remove_bridge_networks.bash"},{"location":"posts/ansible-kvm-router-lab-part-6/#check_uid-user_uid_2","text":"remove_bridge_networks.bash begins by making sure that it is run as a non-privileged user. You can call the script with bash remove_bridge_networks.bash .","title":"check_uid \"${USER_UID}\""},{"location":"posts/ansible-kvm-router-lab-part-6/#function-disable_autostart_bridge_networks","text":"disable_autostart_bridge_networks passes each of the upper and lower bridge network names to disable_autostart_bridge_network , which parses the output of virsh net-info to find out if the network has autostart enabled, and if it is, invokes virsh net-autostart with the --disable option to disable autostart for the given network, and then recursively calls itself for the purpose of verification.","title":"function disable_autostart_bridge_networks()"},{"location":"posts/ansible-kvm-router-lab-part-6/#function-stop_bridge_networks","text":"stop_bridge_networks passes each of the upper and lower bridge network names to stop_bridge_network , which parses the output of virsh net-info in order to find out if the given network is running, and it if is, invokes virsh net-destroy against the given network to stop it, and then recursively calls itself for the purpose of verification.","title":"function stop_bridge_networks()"},{"location":"posts/ansible-kvm-router-lab-part-6/#function-undefine_bridge_networks","text":"undefine_bridge_networks passes each of the upper and lower bridge network names to undefine_bridge_network , which parses the output of virsh net-list --all in order to find out if the given network is defined, and if it is, invokes virsh net-undefine against the given network to undefine it, and then recursively calls itself for the purpose of verification.","title":"function undefine_bridge_networks()"},{"location":"posts/apache-virtual-hosts/","text":"date: 2020-12-20 Use Virtual Hosts This is a very useful way to keep your server organized. Virtual Hosts On Your Lan You can practice on your Lan. Setting up DNS on your Lan For instance, if your router is running dnsmasq , this may be as simple as describing the virtual hosts in /etc/hosts on the router. 192.168.1.101 blog.devbox blogstatic.devbox Here's An Example Reverse Proxy for A Flask Blog On Your Lan # /etc/apache2/sites-enabled/blog.devbox.conf <VirtualHost *:80 > ServerName blog.devbox # dont' block LetsEncrypt # ProxyPass \"/.well-known\" ! ... not needed on your Lan # don't block /var/www/html/favicon.ico ProxyPass \"/favicon.ico\" ! ProxyPass \"/\" \"http://127.0.0.1:8000/\" ProxyPassReverse \"/\" \"http://127.0.0.1:8000/\" ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> Here's An Example for A Static Blog On Your Lan # /etc/apache2/sites-enabled/blogstatic.devbox.conf <VirtualHost *:80 > ServerName blogstatic.devbox DocumentRoot /var/www/html/blogstatic/site ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> Wan Deployment Set up DNS Log into your dns provider and create records A record for blog.example.com pointing to your ipv4 address AAAA record for blog.example.com pointing to your ipv6 address A record for blogstatic.example.com pointing to your ipv4 address AAAA record for blogstatic.example.com pointing to your ipv6 address Start With Virtual Hosts for HTTP You don't need to create virtual hosts for SSL configuration, because CertBot will automatically do that for you. Reverse Proxy # /etc/apache2/sites-enabled/blog.example.com.conf <VirtualHost *:80 > ServerName blog.example.com # dont' block LetsEncrypt ProxyPass \"/.well-known\" ! # don't block /var/www/html/favicon.ico ProxyPass \"/favicon.ico\" ! ProxyPass \"/\" \"http://127.0.0.1:8000/\" ProxyPassReverse \"/\" \"http://127.0.0.1:8000/\" ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> Static Site # /etc/apache2/sites-enabled/blogstatic.example.com.conf <VirtualHost *:80 > ServerName blogstatic.example.com DocumentRoot /var/www/html/blogstatic/site ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> Get LetsEncrypt Certs certbot --apache -d blog.example.com -d blogstatic.example.com Certbot will create and enable new conf files with SSL encryption configured, and will modify your http conf files with redirections to https.","title":"Apache Virtual Hosts"},{"location":"posts/apache-virtual-hosts/#use-virtual-hosts","text":"This is a very useful way to keep your server organized.","title":"Use Virtual Hosts"},{"location":"posts/apache-virtual-hosts/#virtual-hosts-on-your-lan","text":"You can practice on your Lan.","title":"Virtual Hosts On Your Lan"},{"location":"posts/apache-virtual-hosts/#setting-up-dns-on-your-lan","text":"For instance, if your router is running dnsmasq , this may be as simple as describing the virtual hosts in /etc/hosts on the router. 192.168.1.101 blog.devbox blogstatic.devbox","title":"Setting up DNS on your Lan"},{"location":"posts/apache-virtual-hosts/#heres-an-example-reverse-proxy-for-a-flask-blog-on-your-lan","text":"# /etc/apache2/sites-enabled/blog.devbox.conf <VirtualHost *:80 > ServerName blog.devbox # dont' block LetsEncrypt # ProxyPass \"/.well-known\" ! ... not needed on your Lan # don't block /var/www/html/favicon.ico ProxyPass \"/favicon.ico\" ! ProxyPass \"/\" \"http://127.0.0.1:8000/\" ProxyPassReverse \"/\" \"http://127.0.0.1:8000/\" ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>","title":"Here's An Example Reverse Proxy for A Flask Blog On Your Lan"},{"location":"posts/apache-virtual-hosts/#heres-an-example-for-a-static-blog-on-your-lan","text":"# /etc/apache2/sites-enabled/blogstatic.devbox.conf <VirtualHost *:80 > ServerName blogstatic.devbox DocumentRoot /var/www/html/blogstatic/site ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>","title":"Here's An Example for A Static Blog On Your Lan"},{"location":"posts/apache-virtual-hosts/#wan-deployment","text":"","title":"Wan Deployment"},{"location":"posts/apache-virtual-hosts/#set-up-dns","text":"Log into your dns provider and create records A record for blog.example.com pointing to your ipv4 address AAAA record for blog.example.com pointing to your ipv6 address A record for blogstatic.example.com pointing to your ipv4 address AAAA record for blogstatic.example.com pointing to your ipv6 address","title":"Set up DNS"},{"location":"posts/apache-virtual-hosts/#start-with-virtual-hosts-for-http","text":"You don't need to create virtual hosts for SSL configuration, because CertBot will automatically do that for you.","title":"Start With Virtual Hosts for HTTP"},{"location":"posts/apache-virtual-hosts/#reverse-proxy","text":"# /etc/apache2/sites-enabled/blog.example.com.conf <VirtualHost *:80 > ServerName blog.example.com # dont' block LetsEncrypt ProxyPass \"/.well-known\" ! # don't block /var/www/html/favicon.ico ProxyPass \"/favicon.ico\" ! ProxyPass \"/\" \"http://127.0.0.1:8000/\" ProxyPassReverse \"/\" \"http://127.0.0.1:8000/\" ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>","title":"Reverse Proxy"},{"location":"posts/apache-virtual-hosts/#static-site","text":"# /etc/apache2/sites-enabled/blogstatic.example.com.conf <VirtualHost *:80 > ServerName blogstatic.example.com DocumentRoot /var/www/html/blogstatic/site ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>","title":"Static Site"},{"location":"posts/apache-virtual-hosts/#get-letsencrypt-certs","text":"certbot --apache -d blog.example.com -d blogstatic.example.com Certbot will create and enable new conf files with SSL encryption configured, and will modify your http conf files with redirections to https.","title":"Get LetsEncrypt Certs"},{"location":"posts/clear-linux-encrypted-xfs-root/","text":"date: 2019-04-13T21:44:37-07:00 Nothing to-it Burger I had intended to create a technical explanation how to install Clear Linux with disk encryption, with xfs. But that turned out to be unnecessary because the latest version of the installer handles setting that up automatically. Previously, I had written down the steps needed to get LMDE 3 installed using disk encryption with xfs , which required manual intervention. And indeed, a few months ago, the Clear Linux installer only supported xfs with disk encryption if you could supply some manual intervention. However, the latest Clear Linux installer can set up disk encryption with luks and xfs, automatically. Just follow the instructions , no special skills needed.","title":"Clear Linux Encrypted XFS Root"},{"location":"posts/clear-linux-encrypted-xfs-root/#nothing-to-it-burger","text":"I had intended to create a technical explanation how to install Clear Linux with disk encryption, with xfs. But that turned out to be unnecessary because the latest version of the installer handles setting that up automatically. Previously, I had written down the steps needed to get LMDE 3 installed using disk encryption with xfs , which required manual intervention. And indeed, a few months ago, the Clear Linux installer only supported xfs with disk encryption if you could supply some manual intervention. However, the latest Clear Linux installer can set up disk encryption with luks and xfs, automatically. Just follow the instructions , no special skills needed.","title":"Nothing to-it Burger"},{"location":"posts/clear-linux-guest-virt-manager/","text":"date: 2019-03-11T01:39:09-07:00 Introduction download, convert, and resize the provided kvm-legacy image create a virtual machine and launch it from virt-manager But it\u2019s not immediately clear from the instructions if you can use virt-manager , because they recommend their script which runs qemu-system-x86_64 directly. Which is fine, but maybe you find it easier to customize the options using the virt-manager gui interface. How To Assuming you have libvirt and kvm set up with virt-manager , you can: download the clear-*-legacy-kvm.img.xz verify the checksum extract it unxz clear-*-legacy-kvm.img.xz mv clear-*-legacy-kvm.img.xz /var/lib/libvirt/images/ create a virtual machine in virt-manager using the image There is not an os template for Clear Linux, but Fedora29 works fine for me. As a bonus, virsh console is configured and ready to go. Convert Raw -> Qcow2 and Resize The image has a gpt partition table. I am not sure if that is the reason why, but fdisk does not seem to work for resizing the partition. However, parted works fine. The image download is an 8gb sparse raw image. You may wish to convert that to qcow2 and and resize before creating the virtual machine. Here is how to do that. convert the sparse raw image to qcow2 qemu-img convert -f raw -O qcow2 clear*.img clear.qcow2 resize the image to taste qemu-img resize clear.qcow2 20G create the virtual machine in virt-manager gui boot the virtual machine: virsh start clearvm log in: virsh console clearvm install a bundle which contains parted swupd bundle-add clr-installer expand / partition and file system with parted and resize2fs parted /dev/vda resizepart > Fix/Ignore? Fix > Partition number? 1 > End? [8590MB]? 100% > size2fs /dev/vda1","title":"Clear Linux Guest Virt Manager"},{"location":"posts/clear-linux-guest-virt-manager/#introduction","text":"download, convert, and resize the provided kvm-legacy image create a virtual machine and launch it from virt-manager But it\u2019s not immediately clear from the instructions if you can use virt-manager , because they recommend their script which runs qemu-system-x86_64 directly. Which is fine, but maybe you find it easier to customize the options using the virt-manager gui interface.","title":"Introduction"},{"location":"posts/clear-linux-guest-virt-manager/#how-to","text":"Assuming you have libvirt and kvm set up with virt-manager , you can: download the clear-*-legacy-kvm.img.xz verify the checksum extract it unxz clear-*-legacy-kvm.img.xz mv clear-*-legacy-kvm.img.xz /var/lib/libvirt/images/ create a virtual machine in virt-manager using the image There is not an os template for Clear Linux, but Fedora29 works fine for me. As a bonus, virsh console is configured and ready to go.","title":"How To"},{"location":"posts/clear-linux-guest-virt-manager/#convert-raw-qcow2-and-resize","text":"The image has a gpt partition table. I am not sure if that is the reason why, but fdisk does not seem to work for resizing the partition. However, parted works fine. The image download is an 8gb sparse raw image. You may wish to convert that to qcow2 and and resize before creating the virtual machine. Here is how to do that. convert the sparse raw image to qcow2 qemu-img convert -f raw -O qcow2 clear*.img clear.qcow2 resize the image to taste qemu-img resize clear.qcow2 20G create the virtual machine in virt-manager gui boot the virtual machine: virsh start clearvm log in: virsh console clearvm install a bundle which contains parted swupd bundle-add clr-installer expand / partition and file system with parted and resize2fs parted /dev/vda resizepart > Fix/Ignore? Fix > Partition number? 1 > End? [8590MB]? 100% > size2fs /dev/vda1","title":"Convert Raw -&gt; Qcow2 and Resize"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/","text":"date: 2021-09-24 Introduction Performance Your Debian Server is way more powerful than your laptop or desktop and flutter integration_tests suck. Ergonomics You have an Android Emulator (or a real device) connected to the machine that you are sitting in front of for reference, and now you can run integration_tests on a different device without having to juggle adb connections on the same machine. Nspawn Tho? Because containers unlike virtual machines access the full power of the host, but nspawn containers are peristent like virtual machines, sparing you the cognitive overhead of dealing with the ephemerality of docker containers and/or of herding cats. And you already have nspawn, it's build into systemd. Even including the (virtual) network interfaces. Documentation Let's face it: setting up an Android Development Environment is a nightmare. So don't just follow this guide; follow this guide a repetition of three times, building your own step-by-step guide for yourself as you go. Your brain will thank you. Host Preparation (Debian 11) install systemd-container and debootstrap enable unprivileged user namespaces echo 'kernel.unprivileged_userns_clone=1' >/etc/sysctl.d/nspawn.conf systemctl restart systemd-sysctl.service you might as well allow debootstrap to user your apt-cacher-ng proxy export http_proxy=http://<ip address>:3142 br0 bridge describe br0 bridge in /etc/systemd/nspawn/ftest.nspawn (optional). # /etc/systemd/nspawn/ftest.nspawn [Network] VirtualEthernet = yes Bridge = br0 ZFS mountpoint This is optional, obviously; you might not even use zfs. zfs create vm_pool/nspawn/ftest zfs set mountpoint=/var/lib/machines/ftest vm_pool/nspawn/ftest sanity check zfs list -r vm_pool/nspawn bootstrap container # for apt-cacher-ng proxy export http_proxy = http://<ip address>:3142 debootstrap --include = systemd-container stable /var/list/machines/ftest preboot config delete container's package cache copy /etc/apt/apt.conf to container copy /root/.bashrc to container copy /root/.inputrc to container edit /etc/hostname in container write nspawn file on host copy /etc/locale.gen to /etc/locale.gen.bak on container first interactive boot systemd-nspawn -D /var/lib/machines/ftest -U --machine ftest set passwd: passwd stop container: logout run as service systemctl start systemd-nspawn@ftest login: machinectl login ftest start/enable network systemctl enable --now systemd-networkd add regular user useradd <username> install applications locale install locales edit /etc/locale.gen to taste and then run the command locale-gen essential apps apt-get install openssh-server git unzip wget sudo curl file rsync add regular user to sudo group usermod -a -G sudo <user> other apps apt-get install mosh htop haveged byobu needrestart tree bash-completion install openjdk-8 from stretch repo add following to /etc/apt/sources.list deb http://security.debian.org/debian-security stretch/updates main apt-get update && apt-get install openjdk-8-jdk-headless user environment You can now ssh into your container. scp your favorite environment files over to the container ~/.byobu/ ~/.bashrc ~/.bash_aliases ~/.inputrc install flutter Pick a location to taste; I prefer ~/.local/ cd ; cd .local git clone https://github.com/flutter/flutter.git downgrade flutter if needed: cd ~/.local/flutter git checkout 2 .2.3 install command-line-tools The schuck and jive here is absurd, but here goes. Now is the time to decide where ANDROID_HOME and ANDROID_SDK_ROOT are going to be; I prefer ~/.local/share/Android/Sdk/ mkdir -p ~/.local/share/Android/Sdk temporary installation of cmdline-tools Command line tools only Scroll half way down cd ~/.local/share/Android/Sdk wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip unzip commandlinetools-linux-7583922_latest.zip mkdir 5 .0 mv cmdline-tools/* 5 .0/ mv 5 .0 cmdline-tools/ flutter and sdk environment add the following to ~/.bashrc function addToPATH { case \":$PATH:\" in *\":$1:\"*) :;; # already there *) PATH = \"$PATH:$1\";; # or PATH=\"$PATH:$1\" esac } addToPATH ~/.local/flutter/bin addToPATH ~/.local/share/Android/Sdk/cmdline-tools/latest/bin addToPATH ~/.local/share/Android/Sdk/platform-tools # temporary path to temporary version of cmdline-tools addToPATH ~/.local/share/Android/Sdk/cmdline-tools/5.0/bin add the following to ~/.bash_aliases alias sdkmanager = 'sdkmanager --sdk_root=~/.local/share/Android/Sdk' Confirm by logging out and then back in and: which flutter ; which sdkmanager ; alias now install cmdline-tools for real sdkmanager --install \"cmdline-tools;latest\" and then logout and log back in cleanup At this point I think you can remove or comment the temporary PATH statement from ~/.bashrc for the temporary location of cmdline-tools install Android SDK review your options sdkmanager --list and then install them (platform-tools: adb and fastboot will be pulled in automatically) sdkmanager --install \"platforms;android-30\" \\ \"build-tools;31.0.0\" \"build-tools;30.0.3\" confirm flutter installation flutter doctor run tests At this point you shoud be able to rsync a flutter app over to the container, connect to a device using network adb, and run something like: flutter drive --driver integration_test/driver.dart \\ --target integration_test/app_test.dart --profile","title":"Flutter Integration Test Server in Debian 11 Nspawn Container"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#introduction","text":"","title":"Introduction"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#performance","text":"Your Debian Server is way more powerful than your laptop or desktop and flutter integration_tests suck.","title":"Performance"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#ergonomics","text":"You have an Android Emulator (or a real device) connected to the machine that you are sitting in front of for reference, and now you can run integration_tests on a different device without having to juggle adb connections on the same machine.","title":"Ergonomics"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#nspawn-tho","text":"Because containers unlike virtual machines access the full power of the host, but nspawn containers are peristent like virtual machines, sparing you the cognitive overhead of dealing with the ephemerality of docker containers and/or of herding cats. And you already have nspawn, it's build into systemd. Even including the (virtual) network interfaces.","title":"Nspawn Tho?"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#documentation","text":"Let's face it: setting up an Android Development Environment is a nightmare. So don't just follow this guide; follow this guide a repetition of three times, building your own step-by-step guide for yourself as you go. Your brain will thank you.","title":"Documentation"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#host-preparation-debian-11","text":"install systemd-container and debootstrap enable unprivileged user namespaces echo 'kernel.unprivileged_userns_clone=1' >/etc/sysctl.d/nspawn.conf systemctl restart systemd-sysctl.service you might as well allow debootstrap to user your apt-cacher-ng proxy export http_proxy=http://<ip address>:3142","title":"Host Preparation (Debian 11)"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#br0-bridge","text":"describe br0 bridge in /etc/systemd/nspawn/ftest.nspawn (optional). # /etc/systemd/nspawn/ftest.nspawn [Network] VirtualEthernet = yes Bridge = br0","title":"br0 bridge"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#zfs-mountpoint","text":"This is optional, obviously; you might not even use zfs. zfs create vm_pool/nspawn/ftest zfs set mountpoint=/var/lib/machines/ftest vm_pool/nspawn/ftest sanity check zfs list -r vm_pool/nspawn","title":"ZFS mountpoint"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#bootstrap-container","text":"# for apt-cacher-ng proxy export http_proxy = http://<ip address>:3142 debootstrap --include = systemd-container stable /var/list/machines/ftest","title":"bootstrap container"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#preboot-config","text":"delete container's package cache copy /etc/apt/apt.conf to container copy /root/.bashrc to container copy /root/.inputrc to container edit /etc/hostname in container write nspawn file on host copy /etc/locale.gen to /etc/locale.gen.bak on container","title":"preboot config"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#first-interactive-boot","text":"systemd-nspawn -D /var/lib/machines/ftest -U --machine ftest set passwd: passwd stop container: logout","title":"first interactive boot"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#run-as-service","text":"systemctl start systemd-nspawn@ftest login: machinectl login ftest start/enable network systemctl enable --now systemd-networkd add regular user useradd <username>","title":"run as service"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#install-applications","text":"","title":"install applications"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#locale","text":"install locales edit /etc/locale.gen to taste and then run the command locale-gen","title":"locale"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#essential-apps","text":"apt-get install openssh-server git unzip wget sudo curl file rsync","title":"essential apps"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#add-regular-user-to-sudo-group","text":"usermod -a -G sudo <user>","title":"add regular user to sudo group"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#other-apps","text":"apt-get install mosh htop haveged byobu needrestart tree bash-completion","title":"other apps"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#install-openjdk-8-from-stretch-repo","text":"add following to /etc/apt/sources.list deb http://security.debian.org/debian-security stretch/updates main apt-get update && apt-get install openjdk-8-jdk-headless","title":"install openjdk-8 from stretch repo"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#user-environment","text":"You can now ssh into your container. scp your favorite environment files over to the container ~/.byobu/ ~/.bashrc ~/.bash_aliases ~/.inputrc","title":"user environment"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#install-flutter","text":"Pick a location to taste; I prefer ~/.local/ cd ; cd .local git clone https://github.com/flutter/flutter.git","title":"install flutter"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#downgrade-flutter","text":"if needed: cd ~/.local/flutter git checkout 2 .2.3","title":"downgrade flutter"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#install-command-line-tools","text":"The schuck and jive here is absurd, but here goes. Now is the time to decide where ANDROID_HOME and ANDROID_SDK_ROOT are going to be; I prefer ~/.local/share/Android/Sdk/ mkdir -p ~/.local/share/Android/Sdk","title":"install command-line-tools"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#temporary-installation-of-cmdline-tools","text":"Command line tools only Scroll half way down cd ~/.local/share/Android/Sdk wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip unzip commandlinetools-linux-7583922_latest.zip mkdir 5 .0 mv cmdline-tools/* 5 .0/ mv 5 .0 cmdline-tools/","title":"temporary installation of cmdline-tools"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#flutter-and-sdk-environment","text":"add the following to ~/.bashrc function addToPATH { case \":$PATH:\" in *\":$1:\"*) :;; # already there *) PATH = \"$PATH:$1\";; # or PATH=\"$PATH:$1\" esac } addToPATH ~/.local/flutter/bin addToPATH ~/.local/share/Android/Sdk/cmdline-tools/latest/bin addToPATH ~/.local/share/Android/Sdk/platform-tools # temporary path to temporary version of cmdline-tools addToPATH ~/.local/share/Android/Sdk/cmdline-tools/5.0/bin add the following to ~/.bash_aliases alias sdkmanager = 'sdkmanager --sdk_root=~/.local/share/Android/Sdk' Confirm by logging out and then back in and: which flutter ; which sdkmanager ; alias","title":"flutter and sdk environment"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#now-install-cmdline-tools-for-real","text":"sdkmanager --install \"cmdline-tools;latest\" and then logout and log back in","title":"now install cmdline-tools for real"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#cleanup","text":"At this point I think you can remove or comment the temporary PATH statement from ~/.bashrc for the temporary location of cmdline-tools","title":"cleanup"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#install-android-sdk","text":"review your options sdkmanager --list and then install them (platform-tools: adb and fastboot will be pulled in automatically) sdkmanager --install \"platforms;android-30\" \\ \"build-tools;31.0.0\" \"build-tools;30.0.3\"","title":"install Android SDK"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#confirm-flutter-installation","text":"flutter doctor","title":"confirm flutter installation"},{"location":"posts/debian-11-nspawn-flutter-integration-test-server/#run-tests","text":"At this point you shoud be able to rsync a flutter app over to the container, connect to a device using network adb, and run something like: flutter drive --driver integration_test/driver.dart \\ --target integration_test/app_test.dart --profile","title":"run tests"},{"location":"posts/debian-11-ttrss/","text":"date: 2021-09-11 Introduction Install tt-rss on Debian 11 the Debian way. Why? Debian packages tt-rss , so unlike instructions you may find elsewhere, you can depend on the Debian Maintainers to look out for security concerns. And it's easier to install this way. And if I may say, tt-rss runs really well. It's been around for many years now, and the smartphones and vps hosts continue getting more powerful. Apache Install apache2 web server: apt install apache2 Lan If you are installing in a virtual machine on your lan, then this is all you need to do; i.e. later after you have finished installing tt-rss, you will find the following in /etc/tt-rss/apache.conf : Alias /tt-rss /usr/share/tt-rss/www Wan If you deploy on a vps, for instance Linode has Debian 11 images, you definitely want to setup Let's Encrypt Certs. Create a virtual host # /etc/apache2/sites-available/005-rss.example.com.conf <VirtualHost *:80 > ServerName rss.example.com ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> Activate the new virtual host: a2ensite 005-rss.example.com.conf systemctl reload apache2 Certbot install certbot: apt install python3-certbot-apache get certificate certbot --apache -d rss.example.com Verify Certbot Request Your virtual host has been modified. # /etc/apache2/sites-available/005-rss.example.com.conf <VirtualHost *:80 > ServerName rss.example.com ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined RewriteEngine on RewriteCond %{SERVER_NAME} =rss.example.com RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] </VirtualHost> Furthermore, a new virtual host has been created and enabled. # /etc/apache2/sites-available/005-rss.example.com-le-ssl.conf <IfModule mod_ssl.c > <VirtualHost *:443 > ServerName rss.example.com ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLCertificateFile /etc/letsencrypt/live/rss.example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/rss.example.com/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf </VirtualHost> </IfModule> And you should now have a systemd timer to automatically renew your certs: /etc/systemd/system/timers.target.wants/certbot.timer -> /lib/systemd/system/certbot.timer CatchAll VirtualHost You can prevent apache from responding to incorrect subdomains by adding a CatchAll virtual host and enabling it. # /etc/apache2/sites-available/999-catchall.conf <VirtualHost *:80 > ServerName null ServerAlias * Redirect 404 / </VirtualHost> <VirtualHost *:443 > ServerName null ServerAlias * Redirect 404 / </VirtualHost> MariaDB Install mariadb: apt install mariadb-server Setup mariadb: mysql_secure_installation As far as running mysql_secure_installation , I would imagine that you want to remove anonymous users, disallow root login remotely, remove the test database, and reload the privilege table. TT-RSS After installing apache2 and mariadb, install tt-rss: apt install tt-rss . You will be prompted 3 times by dpkg-configure, but it will be obvious what to do. You're done! Open http://examplelanhost/tt-rss or https://rss.example.com/tt-rss , login with the default admin:password and have fun playing with your server. I particularly appreciate the 2fa and opml import. In order to use the Android application check enable API in preferences . All the best blogs still have rss feeds. If you can't find the rss feed for a blog, type Ctrl + U to show page source and look for rss feed url in the head section. Alternately on a mobile phone you can prepend the url with view-source: .","title":"Debian 11 TT-RSS"},{"location":"posts/debian-11-ttrss/#introduction","text":"Install tt-rss on Debian 11 the Debian way.","title":"Introduction"},{"location":"posts/debian-11-ttrss/#why","text":"Debian packages tt-rss , so unlike instructions you may find elsewhere, you can depend on the Debian Maintainers to look out for security concerns. And it's easier to install this way. And if I may say, tt-rss runs really well. It's been around for many years now, and the smartphones and vps hosts continue getting more powerful.","title":"Why?"},{"location":"posts/debian-11-ttrss/#apache","text":"Install apache2 web server: apt install apache2","title":"Apache"},{"location":"posts/debian-11-ttrss/#lan","text":"If you are installing in a virtual machine on your lan, then this is all you need to do; i.e. later after you have finished installing tt-rss, you will find the following in /etc/tt-rss/apache.conf : Alias /tt-rss /usr/share/tt-rss/www","title":"Lan"},{"location":"posts/debian-11-ttrss/#wan","text":"If you deploy on a vps, for instance Linode has Debian 11 images, you definitely want to setup Let's Encrypt Certs.","title":"Wan"},{"location":"posts/debian-11-ttrss/#create-a-virtual-host","text":"# /etc/apache2/sites-available/005-rss.example.com.conf <VirtualHost *:80 > ServerName rss.example.com ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> Activate the new virtual host: a2ensite 005-rss.example.com.conf systemctl reload apache2","title":"Create a virtual host"},{"location":"posts/debian-11-ttrss/#certbot","text":"install certbot: apt install python3-certbot-apache get certificate certbot --apache -d rss.example.com","title":"Certbot"},{"location":"posts/debian-11-ttrss/#verify-certbot-request","text":"Your virtual host has been modified. # /etc/apache2/sites-available/005-rss.example.com.conf <VirtualHost *:80 > ServerName rss.example.com ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined RewriteEngine on RewriteCond %{SERVER_NAME} =rss.example.com RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] </VirtualHost> Furthermore, a new virtual host has been created and enabled. # /etc/apache2/sites-available/005-rss.example.com-le-ssl.conf <IfModule mod_ssl.c > <VirtualHost *:443 > ServerName rss.example.com ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLCertificateFile /etc/letsencrypt/live/rss.example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/rss.example.com/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf </VirtualHost> </IfModule> And you should now have a systemd timer to automatically renew your certs: /etc/systemd/system/timers.target.wants/certbot.timer -> /lib/systemd/system/certbot.timer","title":"Verify Certbot Request"},{"location":"posts/debian-11-ttrss/#catchall-virtualhost","text":"You can prevent apache from responding to incorrect subdomains by adding a CatchAll virtual host and enabling it. # /etc/apache2/sites-available/999-catchall.conf <VirtualHost *:80 > ServerName null ServerAlias * Redirect 404 / </VirtualHost> <VirtualHost *:443 > ServerName null ServerAlias * Redirect 404 / </VirtualHost>","title":"CatchAll VirtualHost"},{"location":"posts/debian-11-ttrss/#mariadb","text":"Install mariadb: apt install mariadb-server Setup mariadb: mysql_secure_installation As far as running mysql_secure_installation , I would imagine that you want to remove anonymous users, disallow root login remotely, remove the test database, and reload the privilege table.","title":"MariaDB"},{"location":"posts/debian-11-ttrss/#tt-rss","text":"After installing apache2 and mariadb, install tt-rss: apt install tt-rss . You will be prompted 3 times by dpkg-configure, but it will be obvious what to do. You're done! Open http://examplelanhost/tt-rss or https://rss.example.com/tt-rss , login with the default admin:password and have fun playing with your server. I particularly appreciate the 2fa and opml import. In order to use the Android application check enable API in preferences . All the best blogs still have rss feeds. If you can't find the rss feed for a blog, type Ctrl + U to show page source and look for rss feed url in the head section. Alternately on a mobile phone you can prepend the url with view-source: .","title":"TT-RSS"},{"location":"posts/faster-partitioning-with-sgdisk/","text":"date: 2019-02-11T04:23:52-08:00 Disclaimer If any of this is wrong, let me know so I can fix it. No actual hard drives were harmed in the production of this blog post. The examples are easier to read if you turn your smart phone sideways. Command Line Is Faster Sure you can partition your discs using a GUI disk management application or an interactive, menu-driven terminal interface. But the command line is faster. gdisk vs sgdisk sgdisk is the scriptable version of gdisk (gptfdisk). what the manpage says If you\u2019re familiar with gdisk , you probably know how to interactively set the partition size and type. If you look at the man page for sgdisk you see that the relevant flags are -n and -t . The beginning and ending numbers are absolute, unless you prepend them with a + or - sign, in which case they become relative. # For New Partition: -n, --new=partnum:start:end # Change partition type: -t, --typecode=partnum:{hexcode|GUID} Example with Separate EFI and / Partitions BTW, gdisk is a partitioning tool intended to be used with a gpt partition table, so the assumption is that you would want an efi partition, (although the efi partition does not have to be on the disk you are partitioning or even on the same disk where your other system partitions are). Wipe any leftover filesystem metadata with wipefs. wipefs --all /dev/sdx Create a new GPT partition table. sgdisk /dev/sdx -o Create an efi partition of 512MB by specifying the end of the partition (relative) and the partition type, ef00 . sgdisk /dev/sdx -n 1::+512MiB -t 1:ef00 Create an / partition using the remainder of the disk, by not specifying the end or the beginning or partition type, which defaults to 8300. sgdisk /dev/sdx -n 2 Format the efi partition fat 32. mkfs.vfat -F32 /dev/sdx1 Format the / partition ext4. mkfs.ext4 /dev/sdx2 Practice With A Sparse Image If you don\u2019t want to partition a real hard drive, you can practice using an sparse image file, instead. # create a sparse image file truncate -S 100G practiceImage.img # partition the image file with sgdisk sgdisk practiceImage.img -o # etc Example with Separate /boot, EFI, and luks-encrypted / Partitions Wipe any leftover filesystem metadata with wipefs . wipefs --all /dev/sdx Create a new GPT partition table. sgdisk /dev/sdx -o Create an efi partition of 512MB by specifying the end of the partition (relative) and the partition type, ef00 . sgdisk /dev/sdx -n 1::+512MiB -t 1:ef00 Create a /boot partition of 1GB, by specifying the end of the partition (relative), but not specifying the partition type which defaults to 8300 . sgdisk /dev/sdx -n 2::+1GiB Create an / partition using the remainder of the disk, by not specifying the end or the beginning or partition type, which defaults to 8300 . sgdisk /dev/sdx -n 3 Format the efi partition fat 32. mkfs.vfat -F32 /dev/sdx1 Format the /boot partition ext4. mkfs.ext4 /dev/sdx2 Encrypt the / partition. cryptsetup -y -v luksFormat --type luks2 /dev/sdx3 Decrypt the / device. cryptsetup open /dev/sdx3 cryptroot Format the / device. mkfs.xfs /dev/mapper/cryptroot What About Swap? I prefer to use a swap file inside the luks-encrypted / partition. But you can make a separate swap partition if you like. Example with 2GB swap partition Wipe the disc. wipefs --all /dev/sdx Create a new GPT partition table. sgdisk /dev/sdx -o Create an EFI partition. sgdisk /dev/sdx -n 1::+512MiB -t 1:ef00 Create a /boot partition. sgdisk /dev/sdx -n 2::+1GiB Create a / partition with a relative negative end. sgdisk /dev/sdx -n 3::-2GiB Create a swap partion type 8200 . sgdisk /dev/sdx -n 4 -t 4:8200 format the partitions. mkfs.vfat -F32 /dev/sdx1 mkfs.ext4 /dev/sdx2 mkfs.xfs /dev/sdx3 mkswap /dev/sdx4 Conclusion Good luck to you. Backup your data first. Kind Regards, Trent","title":"Faster Partitioning with Sgdisk"},{"location":"posts/faster-partitioning-with-sgdisk/#disclaimer","text":"If any of this is wrong, let me know so I can fix it. No actual hard drives were harmed in the production of this blog post. The examples are easier to read if you turn your smart phone sideways.","title":"Disclaimer"},{"location":"posts/faster-partitioning-with-sgdisk/#command-line-is-faster","text":"Sure you can partition your discs using a GUI disk management application or an interactive, menu-driven terminal interface. But the command line is faster.","title":"Command Line Is Faster"},{"location":"posts/faster-partitioning-with-sgdisk/#gdisk-vs-sgdisk","text":"sgdisk is the scriptable version of gdisk (gptfdisk).","title":"gdisk vs sgdisk"},{"location":"posts/faster-partitioning-with-sgdisk/#what-the-manpage-says","text":"If you\u2019re familiar with gdisk , you probably know how to interactively set the partition size and type. If you look at the man page for sgdisk you see that the relevant flags are -n and -t . The beginning and ending numbers are absolute, unless you prepend them with a + or - sign, in which case they become relative. # For New Partition: -n, --new=partnum:start:end # Change partition type: -t, --typecode=partnum:{hexcode|GUID}","title":"what the manpage says"},{"location":"posts/faster-partitioning-with-sgdisk/#example-with-separate-efi-and-partitions","text":"BTW, gdisk is a partitioning tool intended to be used with a gpt partition table, so the assumption is that you would want an efi partition, (although the efi partition does not have to be on the disk you are partitioning or even on the same disk where your other system partitions are). Wipe any leftover filesystem metadata with wipefs. wipefs --all /dev/sdx Create a new GPT partition table. sgdisk /dev/sdx -o Create an efi partition of 512MB by specifying the end of the partition (relative) and the partition type, ef00 . sgdisk /dev/sdx -n 1::+512MiB -t 1:ef00 Create an / partition using the remainder of the disk, by not specifying the end or the beginning or partition type, which defaults to 8300. sgdisk /dev/sdx -n 2 Format the efi partition fat 32. mkfs.vfat -F32 /dev/sdx1 Format the / partition ext4. mkfs.ext4 /dev/sdx2","title":"Example with Separate EFI and / Partitions"},{"location":"posts/faster-partitioning-with-sgdisk/#practice-with-a-sparse-image","text":"If you don\u2019t want to partition a real hard drive, you can practice using an sparse image file, instead. # create a sparse image file truncate -S 100G practiceImage.img # partition the image file with sgdisk sgdisk practiceImage.img -o # etc","title":"Practice With A Sparse Image"},{"location":"posts/faster-partitioning-with-sgdisk/#example-with-separate-boot-efi-and-luks-encrypted-partitions","text":"Wipe any leftover filesystem metadata with wipefs . wipefs --all /dev/sdx Create a new GPT partition table. sgdisk /dev/sdx -o Create an efi partition of 512MB by specifying the end of the partition (relative) and the partition type, ef00 . sgdisk /dev/sdx -n 1::+512MiB -t 1:ef00 Create a /boot partition of 1GB, by specifying the end of the partition (relative), but not specifying the partition type which defaults to 8300 . sgdisk /dev/sdx -n 2::+1GiB Create an / partition using the remainder of the disk, by not specifying the end or the beginning or partition type, which defaults to 8300 . sgdisk /dev/sdx -n 3 Format the efi partition fat 32. mkfs.vfat -F32 /dev/sdx1 Format the /boot partition ext4. mkfs.ext4 /dev/sdx2 Encrypt the / partition. cryptsetup -y -v luksFormat --type luks2 /dev/sdx3 Decrypt the / device. cryptsetup open /dev/sdx3 cryptroot Format the / device. mkfs.xfs /dev/mapper/cryptroot","title":"Example with Separate /boot, EFI, and luks-encrypted / Partitions"},{"location":"posts/faster-partitioning-with-sgdisk/#what-about-swap","text":"I prefer to use a swap file inside the luks-encrypted / partition. But you can make a separate swap partition if you like.","title":"What About Swap?"},{"location":"posts/faster-partitioning-with-sgdisk/#example-with-2gb-swap-partition","text":"Wipe the disc. wipefs --all /dev/sdx Create a new GPT partition table. sgdisk /dev/sdx -o Create an EFI partition. sgdisk /dev/sdx -n 1::+512MiB -t 1:ef00 Create a /boot partition. sgdisk /dev/sdx -n 2::+1GiB Create a / partition with a relative negative end. sgdisk /dev/sdx -n 3::-2GiB Create a swap partion type 8200 . sgdisk /dev/sdx -n 4 -t 4:8200 format the partitions. mkfs.vfat -F32 /dev/sdx1 mkfs.ext4 /dev/sdx2 mkfs.xfs /dev/sdx3 mkswap /dev/sdx4","title":"Example with 2GB swap partition"},{"location":"posts/faster-partitioning-with-sgdisk/#conclusion","text":"Good luck to you. Backup your data first. Kind Regards, Trent","title":"Conclusion"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/","text":"date: 2021-10-19 Introduction I fork-lift upgraded my luks-encrypted Arch Linux installation from a Lenovo T460 to a luks-encrypted, software raid1 mirror on a Dell Precision 3561. It was relatively easy to do. The New Laptop the Dell Precision 3561 runs Arch Linux flawlessly, btw I ordered a new Dell Precision 3561 with the minimal ram and ssd configuration, running Ubuntu 20.04. The plan was to transfer a luks-encrypted Arch Linux from my Lenovo T460 to a luks-encrypted thumbdrive. Then run Arch Linux from the thumbdrive on the new Precision 3561 for a few days while I waited for Amazon to deliver some 1tb Samsung 830 nvme ssds. And then finally to transfer the Arch installation from the thumbdrive onto an luks-encrypted software raid1 mirror on the new Precision 3561. Everything went according to plan, with not a single stumble or mishap; and so I'm documenting here how it went down. The only thing left to do now is to order 64gb of ram, so I can run Android Studio, and continue working on my Android App (which needs some work). And of course update Arch Linux 5 times a day. I use Arch Linux, btw! Creating a Rescue Disk The transfer process I came up with involved two thumb drives: one to serve as a live disk to work from, and the other to temporarily run Arch on the new laptop. So why would I not use an Arch install disk as a live disk? Because I cache Arch packages on my lan using an Nginx reverse-cacheing proxy, which makes it really fast to simply bootstrap (pacstrap) a new Arch installation onto a thumb drive, exactly as I would install Arch anywhere else. Transfering Arch to USB I booted my old T460 from my rescue disk and also plugged the other thumb drive into a usb port. Formatting the Thumb Drive. I opened the target thumbdrive in gdisk interactive partition tool, created a new gpt partition table by pressing o . Then created a 1GB efi partition, type ef00, and a single partition for the remainder of the 256gb thumb. I formatted the efi parition on the thumbdrive: mkfs.vfat -F32 /dev/sdc1 I luks-encrypted the other partition on the thumbdrive: luksFormat -y -v /dev/sdc2 Then I opened the new luks device: cryptsetup open /dev/sdc2 cryptroot And formatted it: mkfs.xfs /dev/mapper/cryptroot Copying the efi partition files to Thumb Drive More specifically in a typical systemd-boot configuration the efi partition contains the entire /boot directory. I mounted the T460's efi partition for Arch Linux: mount /dev/sda5 /mnt2 I mounted the thumbdrive's efi partition: mount /dev/sdc1 /mnt And then copied all the files over: cp -av /mnt2/* /mnt/ I then unmounted the efi partitions: umount /mnt2 ; umount /mnt Copying the / partition files to the Thumb Drive I decrypted the Arch / device on the T460 cryptsetup open /dev/sda6 cryptroot2 And then mounted it: mount /dev/mapper/cryptroot2 /mnt2 ...mounted the / device for the thumbdrive: mount /dev/mapper/cryptroot /mnt And copied the files: rsync -aAXvPH /mnt2/ /mnt/ Rescuing The Thumb Drive via Chroot I unmounted the T460's / device: umount /mnt2 Mounted the thumbdrive's efi partition relative to /mnt mount /dev/sdc /mnt/boot And entered chroot : arch-chroot /mnt Updating fstab for the Thumb Drive I located the UUID of the thumbdrive's efi partition: blkid /dev/sdc1 I located the UUID of the luks device: blkid /dev/mapper/cryptroot And updated /etc/fstab accordingly. # /etc/fstab # /dev/mapper/cryptroot UUID = 391f6062-d8af-4266-a48c-186270d54ef3 / xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 1 # /dev/sdc1 UUID = \"FACA-0B61\" /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 2 ... Rebuilding the Initramfs for the Thumb Drive Still inside chroot I ran the following command to rebuild the initramfs . mkinitcpio -P Updating the systemd-boot Entry for the Thumb Drive I located the UUID of /dev/sdc2 with the following command: blkid /dev/sdc2 And updated /boot/loader/entries/arch.conf accordingly. # /boot/loader/entries/arch.conf title arch linux /vmlinuz-linux initrd /intel-ucode.img initrd /initramfs-linux.img options cryptdevice = UUID=f8c5062a-849d-4c56-bc98-2c93da85090f:cryptroot root=/dev/mapper/cryptroot rw quiet loglevel=3 Running Arch from USB At this point Arch would boot and run flawlessly on the new Dell Precision 3561. I went ahead and changed the hostname, machine-id, ssh-keys, and host_ssh_keys to make it official. While test-driving the new machine, I worked on my Ansible-KVM Router Lab . As configured, the mobile workstation gets great battery life under a light work-load of web browser and ssh terminal work. Satisfied that the new system was going to work out, I ordered a pair of 1tb Samsung 830 nvme ssds, and installed them when they arrived. Transfering Arch to the New ssds After installing new nvme ssds, I booted the Dell Precision 3561 from my rescue disk, and also plugged in the thumbdrive on which my Arch system was installed. Formatting The New NVME ssds I opened each nvme ssd in gdisk , created a new gpt partition table, an 1GB efi partition (type ef00), and for the remainder of each disk created a Linux Raid Parition (type fd00). I formatted one of the efi partitions: mkfs.vfat -F32 /dev/nvme0n1p1 I created a raid array: mdadm --create --verbose --level = 1 --metadata = 1 .2 \\ --raid-devices = 2 /dev/md0 /dev/nvme0n1p2 /dev/nvme1n1p2 I luks-encrypted the new raid array: luksFormat -y -v /dev/md0 I opened the new luks device: cryptsetup open /dev/md0 cryptroot And then Formatted it: mkfs.xfs /dev/mapper/cryptroot Copying the efi partition files to NVME ssd I mounted the thumbdrive's efi partition: mount /dev/sdb1 /mnt2 Then I mounted the laptop's efi partition: mount /dev/nvme0n1p1 /mnt And copied to files to the new efi partition: cp -av /mnt2/* /mnt/ And then I unmounted both efi partitions: umount /mnt2 ; umount /mnt Copying the / partition files to the new NVME ssds First I opened the / luks device on the thumbdrive: cryptsetup open /dev/sdb2 cryptroot2 And then mounted it: mount /dev/mapper/cryptroot2 /mnt2 Then I mounted the laptop's / luks device: mount /dev/mapper/cryptroot /mnt And rsynced the operating system files onto the new laptop: rsync -aAXvPH /mnt2/ /mnt/ Rescuing The New Laptop via Chroot First I unmounted the thumbdrive: umount /mnt2 Then I mounted the efi partition relative to /mnt : mount /dev/nvme0n1p1 /mnt/boot And entered chroot ; arch-chroot /mnt Updating fstab for The New Laptop I used to the following command to discover the UUID of the / device: blkid /dev/mapper/cryptroot And a similar command to find the UUID of the efi partition: blkid /dev/nvme0n1p1 Then I editted /etc/fstab to describe the above two UUIDs. # /etc/fstab # /dev/mapper/cryptroot UUID = 3486b7d1-ccc9-43dc-b8ab-abcf71aea90f / xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,sunit=32,swidth=256,noquota 0 1 # /dev/nvme0n1p1 UUID = 9FE0-2A98 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 2 ... Updating mdadm.conf and mkinitcpio.conf When I boot the laptop, the initramfs must assemble the raid array, which requires the following configuration details. I appended a description of the raid array to the bottom of /etc/mdadm.conf by running the following command. mdadm --detail --scan >> /etc/mdadm.conf Then I editted /etc/mkinitcpio.conf to require the mdadm_udev hook. # /etc/mkinitcpio.conf ... # change this HOOKS = (base udev autodetect modconf block encrypt filesystems keyboard fsck) # to this HOOKS = (base udev autodetect modconf block mdadm_udev encrypt filesystems keyboard fsck) ... And then finally rebuilt the initramfs : mkinitcpio -P Updating the systemd-boot Entry for the New Laptop The final step was to update /boot/loader/entries/arch.conf . As explained above, the initramfs assembles the raid device, so I just need to tell the kernel about it. I used the following command to discover the UUID of the raid1 device: blkid /dev/md0 And then updated /boot/loader/entries/arch.conf accordingly. title arch linux /vmlinuz-linux initrd /intel-ucode.img initrd /initramfs-linux.img options cryptdevice = UUID=48f782a9-6c1b-4242-84f9-66b20ff27845:cryptroot root=/dev/mapper/cryptroot rw quiet loglevel=3","title":"Forklift Upgrade Arch Linux To A Dell Precision 3561"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#introduction","text":"I fork-lift upgraded my luks-encrypted Arch Linux installation from a Lenovo T460 to a luks-encrypted, software raid1 mirror on a Dell Precision 3561. It was relatively easy to do.","title":"Introduction"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#the-new-laptop","text":"the Dell Precision 3561 runs Arch Linux flawlessly, btw I ordered a new Dell Precision 3561 with the minimal ram and ssd configuration, running Ubuntu 20.04. The plan was to transfer a luks-encrypted Arch Linux from my Lenovo T460 to a luks-encrypted thumbdrive. Then run Arch Linux from the thumbdrive on the new Precision 3561 for a few days while I waited for Amazon to deliver some 1tb Samsung 830 nvme ssds. And then finally to transfer the Arch installation from the thumbdrive onto an luks-encrypted software raid1 mirror on the new Precision 3561. Everything went according to plan, with not a single stumble or mishap; and so I'm documenting here how it went down. The only thing left to do now is to order 64gb of ram, so I can run Android Studio, and continue working on my Android App (which needs some work). And of course update Arch Linux 5 times a day. I use Arch Linux, btw!","title":"The New Laptop"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#creating-a-rescue-disk","text":"The transfer process I came up with involved two thumb drives: one to serve as a live disk to work from, and the other to temporarily run Arch on the new laptop. So why would I not use an Arch install disk as a live disk? Because I cache Arch packages on my lan using an Nginx reverse-cacheing proxy, which makes it really fast to simply bootstrap (pacstrap) a new Arch installation onto a thumb drive, exactly as I would install Arch anywhere else.","title":"Creating a Rescue Disk"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#transfering-arch-to-usb","text":"I booted my old T460 from my rescue disk and also plugged the other thumb drive into a usb port.","title":"Transfering Arch to USB"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#formatting-the-thumb-drive","text":"I opened the target thumbdrive in gdisk interactive partition tool, created a new gpt partition table by pressing o . Then created a 1GB efi partition, type ef00, and a single partition for the remainder of the 256gb thumb. I formatted the efi parition on the thumbdrive: mkfs.vfat -F32 /dev/sdc1 I luks-encrypted the other partition on the thumbdrive: luksFormat -y -v /dev/sdc2 Then I opened the new luks device: cryptsetup open /dev/sdc2 cryptroot And formatted it: mkfs.xfs /dev/mapper/cryptroot","title":"Formatting the Thumb Drive."},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#copying-the-efi-partition-files-to-thumb-drive","text":"More specifically in a typical systemd-boot configuration the efi partition contains the entire /boot directory. I mounted the T460's efi partition for Arch Linux: mount /dev/sda5 /mnt2 I mounted the thumbdrive's efi partition: mount /dev/sdc1 /mnt And then copied all the files over: cp -av /mnt2/* /mnt/ I then unmounted the efi partitions: umount /mnt2 ; umount /mnt","title":"Copying the efi partition files to Thumb Drive"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#copying-the-partition-files-to-the-thumb-drive","text":"I decrypted the Arch / device on the T460 cryptsetup open /dev/sda6 cryptroot2 And then mounted it: mount /dev/mapper/cryptroot2 /mnt2 ...mounted the / device for the thumbdrive: mount /dev/mapper/cryptroot /mnt And copied the files: rsync -aAXvPH /mnt2/ /mnt/","title":"Copying the / partition files to the Thumb Drive"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#rescuing-the-thumb-drive-via-chroot","text":"I unmounted the T460's / device: umount /mnt2 Mounted the thumbdrive's efi partition relative to /mnt mount /dev/sdc /mnt/boot And entered chroot : arch-chroot /mnt","title":"Rescuing The Thumb Drive via Chroot"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#updating-fstab-for-the-thumb-drive","text":"I located the UUID of the thumbdrive's efi partition: blkid /dev/sdc1 I located the UUID of the luks device: blkid /dev/mapper/cryptroot And updated /etc/fstab accordingly. # /etc/fstab # /dev/mapper/cryptroot UUID = 391f6062-d8af-4266-a48c-186270d54ef3 / xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 1 # /dev/sdc1 UUID = \"FACA-0B61\" /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 2 ...","title":"Updating fstab for the Thumb Drive"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#rebuilding-the-initramfs-for-the-thumb-drive","text":"Still inside chroot I ran the following command to rebuild the initramfs . mkinitcpio -P","title":"Rebuilding the Initramfs for the Thumb Drive"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#updating-the-systemd-boot-entry-for-the-thumb-drive","text":"I located the UUID of /dev/sdc2 with the following command: blkid /dev/sdc2 And updated /boot/loader/entries/arch.conf accordingly. # /boot/loader/entries/arch.conf title arch linux /vmlinuz-linux initrd /intel-ucode.img initrd /initramfs-linux.img options cryptdevice = UUID=f8c5062a-849d-4c56-bc98-2c93da85090f:cryptroot root=/dev/mapper/cryptroot rw quiet loglevel=3","title":"Updating the systemd-boot Entry for the Thumb Drive"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#running-arch-from-usb","text":"At this point Arch would boot and run flawlessly on the new Dell Precision 3561. I went ahead and changed the hostname, machine-id, ssh-keys, and host_ssh_keys to make it official. While test-driving the new machine, I worked on my Ansible-KVM Router Lab . As configured, the mobile workstation gets great battery life under a light work-load of web browser and ssh terminal work. Satisfied that the new system was going to work out, I ordered a pair of 1tb Samsung 830 nvme ssds, and installed them when they arrived.","title":"Running Arch from USB"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#transfering-arch-to-the-new-ssds","text":"After installing new nvme ssds, I booted the Dell Precision 3561 from my rescue disk, and also plugged in the thumbdrive on which my Arch system was installed.","title":"Transfering Arch to the New ssds"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#formatting-the-new-nvme-ssds","text":"I opened each nvme ssd in gdisk , created a new gpt partition table, an 1GB efi partition (type ef00), and for the remainder of each disk created a Linux Raid Parition (type fd00). I formatted one of the efi partitions: mkfs.vfat -F32 /dev/nvme0n1p1 I created a raid array: mdadm --create --verbose --level = 1 --metadata = 1 .2 \\ --raid-devices = 2 /dev/md0 /dev/nvme0n1p2 /dev/nvme1n1p2 I luks-encrypted the new raid array: luksFormat -y -v /dev/md0 I opened the new luks device: cryptsetup open /dev/md0 cryptroot And then Formatted it: mkfs.xfs /dev/mapper/cryptroot","title":"Formatting The New NVME ssds"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#copying-the-efi-partition-files-to-nvme-ssd","text":"I mounted the thumbdrive's efi partition: mount /dev/sdb1 /mnt2 Then I mounted the laptop's efi partition: mount /dev/nvme0n1p1 /mnt And copied to files to the new efi partition: cp -av /mnt2/* /mnt/ And then I unmounted both efi partitions: umount /mnt2 ; umount /mnt","title":"Copying the efi partition files to NVME ssd"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#copying-the-partition-files-to-the-new-nvme-ssds","text":"First I opened the / luks device on the thumbdrive: cryptsetup open /dev/sdb2 cryptroot2 And then mounted it: mount /dev/mapper/cryptroot2 /mnt2 Then I mounted the laptop's / luks device: mount /dev/mapper/cryptroot /mnt And rsynced the operating system files onto the new laptop: rsync -aAXvPH /mnt2/ /mnt/","title":"Copying the / partition files to the new NVME ssds"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#rescuing-the-new-laptop-via-chroot","text":"First I unmounted the thumbdrive: umount /mnt2 Then I mounted the efi partition relative to /mnt : mount /dev/nvme0n1p1 /mnt/boot And entered chroot ; arch-chroot /mnt","title":"Rescuing The New Laptop via Chroot"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#updating-fstab-for-the-new-laptop","text":"I used to the following command to discover the UUID of the / device: blkid /dev/mapper/cryptroot And a similar command to find the UUID of the efi partition: blkid /dev/nvme0n1p1 Then I editted /etc/fstab to describe the above two UUIDs. # /etc/fstab # /dev/mapper/cryptroot UUID = 3486b7d1-ccc9-43dc-b8ab-abcf71aea90f / xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,sunit=32,swidth=256,noquota 0 1 # /dev/nvme0n1p1 UUID = 9FE0-2A98 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 2 ...","title":"Updating fstab for The New Laptop"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#updating-mdadmconf-and-mkinitcpioconf","text":"When I boot the laptop, the initramfs must assemble the raid array, which requires the following configuration details. I appended a description of the raid array to the bottom of /etc/mdadm.conf by running the following command. mdadm --detail --scan >> /etc/mdadm.conf Then I editted /etc/mkinitcpio.conf to require the mdadm_udev hook. # /etc/mkinitcpio.conf ... # change this HOOKS = (base udev autodetect modconf block encrypt filesystems keyboard fsck) # to this HOOKS = (base udev autodetect modconf block mdadm_udev encrypt filesystems keyboard fsck) ... And then finally rebuilt the initramfs : mkinitcpio -P","title":"Updating mdadm.conf and mkinitcpio.conf"},{"location":"posts/forklift-upgrade-arch-linux-precision-3561/#updating-the-systemd-boot-entry-for-the-new-laptop","text":"The final step was to update /boot/loader/entries/arch.conf . As explained above, the initramfs assembles the raid device, so I just need to tell the kernel about it. I used the following command to discover the UUID of the raid1 device: blkid /dev/md0 And then updated /boot/loader/entries/arch.conf accordingly. title arch linux /vmlinuz-linux initrd /intel-ucode.img initrd /initramfs-linux.img options cryptdevice = UUID=48f782a9-6c1b-4242-84f9-66b20ff27845:cryptroot root=/dev/mapper/cryptroot rw quiet loglevel=3","title":"Updating the systemd-boot Entry for the New Laptop"},{"location":"posts/instructions-for-tethering-from-phone/","text":"date: 2020-12-17 Instructions Part One Turn off blutooth on computer Turn off blutooth on phone Turn off Wifi on phone Part Two Turn on wifi hotspot or usb tethering Verify! You want to verify that you are connected to your Android hotspot. Android tether is a router that will stand up a subnet of 192.168.43.0/24 for wifi hotspot, and 192.168.42.0/24 for usb tether. On linux open a terminal and type ip addr on Windows open a cmd console and type ipconfig If tethering via wifi hotspot you should see an ipv4 address of 192.168.43.XX If tethering via usb you should see an ipv4 address of 192.168.42.XX ACHTUNG Do Not! DO NOT turn on ethernet tethering you probably don't have the physical equipment available to do that DO NOT turn on blutooth tethering $# ?# DUH!! DO NOT turn on blutooth Your phone's radio hardware already has enough to do, trying to simultaneously maintain an LTE uplink to your mobile carrier and a wifi downlink to your computer DO NOT turn on wifi Don't let your phone try to connect to something that isn't working right now DO NOT forget to turn off blutooth Your phone's radio hardware already has enough to do, trying to simultaneously maintain an LTE uplink to your mobile carrier and a wifi downlink to your computer DO NOT forget to turn off wifi Don't let your phone try to connect to something that isn't working right now Blutooth If you leave blutooth on while trying to tether, your phone could get hot, your battery could go dead, and your hotspot could fail to work properly. Be surprised it it doesn't crash and soft-reboot.","title":"Instruction For Tethering From Phone"},{"location":"posts/instructions-for-tethering-from-phone/#instructions","text":"","title":"Instructions"},{"location":"posts/instructions-for-tethering-from-phone/#part-one","text":"Turn off blutooth on computer Turn off blutooth on phone Turn off Wifi on phone","title":"Part One"},{"location":"posts/instructions-for-tethering-from-phone/#part-two","text":"Turn on wifi hotspot or usb tethering","title":"Part Two"},{"location":"posts/instructions-for-tethering-from-phone/#verify","text":"You want to verify that you are connected to your Android hotspot. Android tether is a router that will stand up a subnet of 192.168.43.0/24 for wifi hotspot, and 192.168.42.0/24 for usb tether. On linux open a terminal and type ip addr on Windows open a cmd console and type ipconfig If tethering via wifi hotspot you should see an ipv4 address of 192.168.43.XX If tethering via usb you should see an ipv4 address of 192.168.42.XX","title":"Verify!"},{"location":"posts/instructions-for-tethering-from-phone/#achtung-do-not","text":"DO NOT turn on ethernet tethering you probably don't have the physical equipment available to do that DO NOT turn on blutooth tethering $# ?# DUH!! DO NOT turn on blutooth Your phone's radio hardware already has enough to do, trying to simultaneously maintain an LTE uplink to your mobile carrier and a wifi downlink to your computer DO NOT turn on wifi Don't let your phone try to connect to something that isn't working right now DO NOT forget to turn off blutooth Your phone's radio hardware already has enough to do, trying to simultaneously maintain an LTE uplink to your mobile carrier and a wifi downlink to your computer DO NOT forget to turn off wifi Don't let your phone try to connect to something that isn't working right now","title":"ACHTUNG Do Not!"},{"location":"posts/instructions-for-tethering-from-phone/#blutooth","text":"If you leave blutooth on while trying to tether, your phone could get hot, your battery could go dead, and your hotspot could fail to work properly. Be surprised it it doesn't crash and soft-reboot.","title":"Blutooth"},{"location":"posts/kvm-on-arch/","text":"date: 2021-10-07 Introduction This is not intended to be a tutorial, but rather a walk-through of how I would install libvirt/kvm on Arch Linux . Packages iptables-nft dnsmasq bridge-utils openbsd-netcat libvirt qemu-headless virt-install virt-install is not needed if connecting remotely with virt-manager, but it does provide virt-clone . Configuration enable libvirtd service systemctl enable libvirtd add user to libvirt group usermod -a -G libvirt <user> environment/bashrc # ~/.bashrc export LIBVIRT_DEFAULT_URI = \"qemu:///system\" reboot the machine Network The default network is defined in /etc/libvirt/qemu/networks/default.xml . Start the default network virsh net-start default . Permanently enable the default network virsh net-autostart default . Jump Host With virt-manager Abstract your jump host in ~/.ssh/config # ~/.ssh/config Host jumphost Hostname <ip address> Port 22 User <user> Host kvmhost Hostname <ip address> ProxyJump jumphost Port 22 User <user> Now you can connect virt-manager to <user>@kvmhost Console Access Enable serial console on guest. systemctl enable serial-getty@ttyS0.service Nested KVM I was going to try to figure out how to permantly set the cpu mode default such that all virtualmachines will be capable of nested virtualization, but it already is. Perhaps that is the default in virt-manager now? Anyway, in case you want to make sure nested virtualization is enabled in the host kernel. Clone Ip Address Conflict I found a great tutorial for assigning ip addresses . The problem we need to solve here is that virtual machine clones won't necessarily solicit a unique ip address, although a clone will have a new mac address . So, you clone a vm: virt-clone --original arch --name archone --auto-clone Get the clone's mac address: virsh dumpxml archone | grep mac Now assign the clone a dhcp reservation: virsh net-edit default Notice that I tighten up the dhcp range, and add a reservation outside the new dhcp range. <network connections= '1' > <name> default </name> <uuid> 8013c9a5-606f-48a0-a3ec-1cf097e76fb1 </uuid> <forward mode= 'nat' > <nat> <port start= '1024' end= '65535' /> </nat> </forward> <bridge name= 'virbr0' stp= 'on' delay= '0' /> <mac address= '52:54:00:ef:cb:d2' /> <ip address= '192.168.122.1' netmask= '255.255.255.0' > <dhcp> <!-- previous dhcp range <range start='192.168.122.2' end='192.168.122.254'/> --> <!-- begin new lines --> <range start= '192.168.122.50' end= '192.168.122.150' /> <host mac= '52:54:00:cd:7d:7f' name= 'archone' ip= '192.168.122.25' /> <!-- end new lines --> </dhcp> </ip> </network> Restart Default Network virsh net-destroy default virsh net-start default","title":"KVM On Arch"},{"location":"posts/kvm-on-arch/#introduction","text":"This is not intended to be a tutorial, but rather a walk-through of how I would install libvirt/kvm on Arch Linux .","title":"Introduction"},{"location":"posts/kvm-on-arch/#packages","text":"iptables-nft dnsmasq bridge-utils openbsd-netcat libvirt qemu-headless virt-install virt-install is not needed if connecting remotely with virt-manager, but it does provide virt-clone .","title":"Packages"},{"location":"posts/kvm-on-arch/#configuration","text":"enable libvirtd service systemctl enable libvirtd add user to libvirt group usermod -a -G libvirt <user>","title":"Configuration"},{"location":"posts/kvm-on-arch/#environmentbashrc","text":"# ~/.bashrc export LIBVIRT_DEFAULT_URI = \"qemu:///system\" reboot the machine","title":"environment/bashrc"},{"location":"posts/kvm-on-arch/#network","text":"The default network is defined in /etc/libvirt/qemu/networks/default.xml . Start the default network virsh net-start default . Permanently enable the default network virsh net-autostart default .","title":"Network"},{"location":"posts/kvm-on-arch/#jump-host-with-virt-manager","text":"Abstract your jump host in ~/.ssh/config # ~/.ssh/config Host jumphost Hostname <ip address> Port 22 User <user> Host kvmhost Hostname <ip address> ProxyJump jumphost Port 22 User <user> Now you can connect virt-manager to <user>@kvmhost","title":"Jump Host With virt-manager"},{"location":"posts/kvm-on-arch/#console-access","text":"Enable serial console on guest. systemctl enable serial-getty@ttyS0.service","title":"Console Access"},{"location":"posts/kvm-on-arch/#nested-kvm","text":"I was going to try to figure out how to permantly set the cpu mode default such that all virtualmachines will be capable of nested virtualization, but it already is. Perhaps that is the default in virt-manager now? Anyway, in case you want to make sure nested virtualization is enabled in the host kernel.","title":"Nested KVM"},{"location":"posts/kvm-on-arch/#clone-ip-address-conflict","text":"I found a great tutorial for assigning ip addresses . The problem we need to solve here is that virtual machine clones won't necessarily solicit a unique ip address, although a clone will have a new mac address . So, you clone a vm: virt-clone --original arch --name archone --auto-clone Get the clone's mac address: virsh dumpxml archone | grep mac","title":"Clone Ip Address Conflict"},{"location":"posts/kvm-on-arch/#now-assign-the-clone-a-dhcp-reservation","text":"virsh net-edit default Notice that I tighten up the dhcp range, and add a reservation outside the new dhcp range. <network connections= '1' > <name> default </name> <uuid> 8013c9a5-606f-48a0-a3ec-1cf097e76fb1 </uuid> <forward mode= 'nat' > <nat> <port start= '1024' end= '65535' /> </nat> </forward> <bridge name= 'virbr0' stp= 'on' delay= '0' /> <mac address= '52:54:00:ef:cb:d2' /> <ip address= '192.168.122.1' netmask= '255.255.255.0' > <dhcp> <!-- previous dhcp range <range start='192.168.122.2' end='192.168.122.254'/> --> <!-- begin new lines --> <range start= '192.168.122.50' end= '192.168.122.150' /> <host mac= '52:54:00:cd:7d:7f' name= 'archone' ip= '192.168.122.25' /> <!-- end new lines --> </dhcp> </ip> </network>","title":"Now assign the clone a dhcp reservation:"},{"location":"posts/kvm-on-arch/#restart-default-network","text":"virsh net-destroy default virsh net-start default","title":"Restart Default Network"},{"location":"posts/linux-move-cursor-with-keyboard/","text":"date: 2020-06-21T22:01:35-07:00 Introduction Linux just makes everything so easy. On a laptop it can be tricky to place your mouse cursor on exactly the correct pixel, using the touchpad. This became apparent to myself while using GIMP to create some png button files for a little tkinter project, but there must be other use-cases as well. xdo commands for moving the cursor move the cursor one pixel left: xdotool mousemove_relative -- -1 0 move the cursor one pixel right: xdotool mousemove_relative -- 1 0 move the cursor one pixel up: xdotool mousemove_relative -- 0 -1 move the cursor one pixel down: xdotool mousemove_relative -- 0 1 map keyboard shortcuts Now, in your keyboard settings, map the above commands to new custom shortcuts. For instance, I find the Ctrl + Super + Up Ctrl + Super + Down Ctrl + Super + Left Ctrl + Super + Right combinations to be convenient in the Mate Desktop. Enjoy!","title":"Linux Move Cursor With Keyboard"},{"location":"posts/linux-move-cursor-with-keyboard/#introduction","text":"Linux just makes everything so easy. On a laptop it can be tricky to place your mouse cursor on exactly the correct pixel, using the touchpad. This became apparent to myself while using GIMP to create some png button files for a little tkinter project, but there must be other use-cases as well.","title":"Introduction"},{"location":"posts/linux-move-cursor-with-keyboard/#xdo-commands-for-moving-the-cursor","text":"move the cursor one pixel left: xdotool mousemove_relative -- -1 0 move the cursor one pixel right: xdotool mousemove_relative -- 1 0 move the cursor one pixel up: xdotool mousemove_relative -- 0 -1 move the cursor one pixel down: xdotool mousemove_relative -- 0 1","title":"xdo commands for moving the cursor"},{"location":"posts/linux-move-cursor-with-keyboard/#map-keyboard-shortcuts","text":"Now, in your keyboard settings, map the above commands to new custom shortcuts. For instance, I find the Ctrl + Super + Up Ctrl + Super + Down Ctrl + Super + Left Ctrl + Super + Right combinations to be convenient in the Mate Desktop. Enjoy!","title":"map keyboard shortcuts"},{"location":"posts/lmde3-xfs-full-disk-encryption/","text":"date: 2019-01-25T23:25:36-08:00 Introduction Linux Mint Debian Edition is the alternate version of Linux Mint, but built on a Debian base. The result is quite pleasant: the stability of desktop Debian, but with the rough edges polished smooth, nicely configured fonts and ui, and all the multi-media codecs included. Unfortunately, the LMDE 3 installer does not support disk encryption, but manually setting this up by hand is pretty straightforward. On the other hand, manually setting up your partitions by hand allows extra freedom and flexibility, and so I have chosen a simple luks-encrypted / partition formatted xfs. As far as swap is concerned, my preference is to use a swap file instead of a swap partition. Having a swap file instead of a swap partition is more flexible because obviously you can easily recreate a different size swap file whenever you like (or use none at all), and the encryption requires no extra set up because the / partition is encrypted anyway. Will this work with a dual-boot set up? Of course! Because you have to manually configure the partitions anyway, just arrange them exactly how you would need for dual-boot. Assumes uefi-configured boot, with separate partitions for /boot formatted ext4, /boot/efi formatted fat32, and a regular luks-encrypted partition for / formatted xfs. Prepare The Installation Media Visit the Linux Mint Website and download the iso file for LMDE 3 64bit. Download from torrents if possible, to save bandwidth. verify the sha256 sum of the iso file sha256sum lmde-3-201808-cinnamon-64bit.iso Identify the thumb drive you are going to install from. type lsblk , note the output, and then insert the thumb drive then type lsblk again and note the additional output # lsblk /dev/sdb NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sdb 8:32 1 14.5G 0 disk \u251c\u2500sdb1 8:33 1 3.4G 0 part /media/trent/Debian 9.6.0 amd64 \u2514\u2500sdb2 8:34 1 416K 0 part In the above example output we see that our thumb drive is identified as /dev/sdb , and partition /dev/sdb1 is automatically mounted. Take special care that you have accurately identified the thumb drive before proceeding. For the sake of example, we will proceed on the assumption that our thumb drive is identified as /dev/sdb , but you need to compensate accordingly. unmount any partition of the thumb drive that are automatically mounted umount /dev/sdb1 write the disk image to the thumb drive ddrescue -D --force lmde-3-201808-cinnamon-64bit.iso /dev/sdb Boot The Install Disc boot into bios to disable fastboot and secureboot invoke your machine's device boot menu and boot the install disc in uefi mode confirm that you have booted in uefi mode by listing efivars ls /sys/firmware/efi/vars Partition The Hard Drive If you recall we are assuming the target hard drive is /dev/sda , as an example. So, make adjustments as necessary. If you would rather use a different partition tool, make sure the efi partition is an efi partition type, and you definitely need a separate /boot partition. if needed you can clear the drive with wipefs wipefs --all /dev/sda create a new partition table for /dev/sda sgdisk /dev/sda -o create a new efi partition for /dev/sda sgdisk /dev/sda --new=1::+512MiB --typecode=1:ef00 create a new /boot partition for /dev/sda sgdisk /dev/sda --new=2::+1G create a new / partition for /dev/sda sgdisk /dev/sda --new=3 verify your partition work sgdisk /dev/sda -p format the efi partition mkfs.vfat -F32 /dev/sda1 format the /boot partition mkfs.ext4 /dev/sda2 encrypt the / partition, you will be prompted for a password cryptsetup -y -v luksFormat --type luks2 /dev/sda3 decrypt the / partition, you will be prompted for a password cryptsetup open /dev/sda3 cryptroot format the / device mkfs.xfs /dev/mapper/cryptroot Mount The Hard Drive This takes advantage of expert mode in the LMDE installer. create an /target directory mkdir /target mount the / device at /target mount /dev/mapper/cryptroot /target create an /target/boot directory mkdir /target/boot mount the /boot partition at /target/boot mount /dev/sda2 /target/boot create an /target/boot/efi directory mkdir /target/boot/efi mount the efi partition at /target/boot/efi mount /dev/sda1 /target/boot/efi Run The Installer App At this point you're ready to run the live installer. You can click the disc icon on the desktop. The first three pages of the live-installer cover Language,Timezone, and Keymap. The fourth page of the live-installer covers name, password, and hostname. On the fifth page of the live-installer, you come to a partition configuration page. But there is nothing to do, so select expert mode at the bottom of the page. Again select forward , and when you come to the page where you configure the location to install grub, that should be the efi partition, i.e. /dev/sda1 . Select forward one more time, and then select install. The installation will run for a few minutes and will then pause. During the pause you need to manually configure fstab and crypttab . Configure Fstab find the UUID of the efi partition blkid /dev/sda1 -s UUID find the UUID of the /boot partition blkid /dev/sda2 -s UUID find the UUID of the / device blkid /dev/mapper/cryptroot -s UUID And when you find the correct UUID numbers, use them to configure /etc/fstab which is actually currently at /target/etc/fstab . # /etc/fstab ############### # efi partition # run the command `blkid /dev/sda1 -s UUID` which outputs # /dev/sda1: UUID=\"17C4-215D\", from which derive UUID=17C4-215D /boot/efi vfat defaults 0 2 # /boot partition # run the command `blkid /dev/sda2 -s UUID` which outputs # /dev/sda2: UUID=\"f2509fff-4854-4721-b546-0274c89e6aec\", from which derive UUID=f2509fff-4854-4721-b546-0274c89e6aec /boot ext4 defaults 0 2 # \"/\" device # run the command `blkid /dev/mapper/cryptroot -s UUID` which outputs # /dev/mapper/cryptroot: UUID=\"72241377-cd65-43a6-8363-1afce5bd93f6\", from which derive UUID=72241377-cd65-43a6-8363-1afce5bd93f6 / xfs defaults 0 1 Configure Crypttab But before the file systems can be mounted, crypttab needs to mount /dev/sda3 at /dev/mapper/cryptroot . Configure /etc/crypttab which is actually currently at /target/etc/crypttab find the UUID of the partition that will be mounted at /dev/mapper/crypttab blkid /dev/sda3 -s UUID And when you find the correct UUID number for /dev/sda3 , use that to configure /etc/crypttab which is actually currently at /target/etc/crypttab . # /etc/crypttab # run the command `blkid /dev/sda3 -s UUID` which outputs # /dev/sda3: UUID=\"da3e0967-711f-4159-85ac-7d5743a75201\", from which derive # <target name> <source device> <key file> <options> cryptroot UUID=da3e0967-711f-4159-85ac-7d5743a75201 none luks Resume Installer App At this point finish running the live installer, and you'll be done. UEFI Fix On some machines, such as HP Laptops, UEFI is broken and efi boot entries don't persist. remount the efi parition mount /dev/sda1 /mnt/ ; cd /mnt/EFI/ create a default efi executable mkdir BOOT ; cp linuxmint/grubx64.efi BOOT/BOOTX64.efi Optional Swap File Visit the Arch Wiki and they will hook you up.","title":"LMDE3 XFS Full Disk Encryption"},{"location":"posts/lmde3-xfs-full-disk-encryption/#introduction","text":"Linux Mint Debian Edition is the alternate version of Linux Mint, but built on a Debian base. The result is quite pleasant: the stability of desktop Debian, but with the rough edges polished smooth, nicely configured fonts and ui, and all the multi-media codecs included. Unfortunately, the LMDE 3 installer does not support disk encryption, but manually setting this up by hand is pretty straightforward. On the other hand, manually setting up your partitions by hand allows extra freedom and flexibility, and so I have chosen a simple luks-encrypted / partition formatted xfs. As far as swap is concerned, my preference is to use a swap file instead of a swap partition. Having a swap file instead of a swap partition is more flexible because obviously you can easily recreate a different size swap file whenever you like (or use none at all), and the encryption requires no extra set up because the / partition is encrypted anyway. Will this work with a dual-boot set up? Of course! Because you have to manually configure the partitions anyway, just arrange them exactly how you would need for dual-boot. Assumes uefi-configured boot, with separate partitions for /boot formatted ext4, /boot/efi formatted fat32, and a regular luks-encrypted partition for / formatted xfs.","title":"Introduction"},{"location":"posts/lmde3-xfs-full-disk-encryption/#prepare-the-installation-media","text":"Visit the Linux Mint Website and download the iso file for LMDE 3 64bit. Download from torrents if possible, to save bandwidth. verify the sha256 sum of the iso file sha256sum lmde-3-201808-cinnamon-64bit.iso Identify the thumb drive you are going to install from. type lsblk , note the output, and then insert the thumb drive then type lsblk again and note the additional output # lsblk /dev/sdb NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sdb 8:32 1 14.5G 0 disk \u251c\u2500sdb1 8:33 1 3.4G 0 part /media/trent/Debian 9.6.0 amd64 \u2514\u2500sdb2 8:34 1 416K 0 part In the above example output we see that our thumb drive is identified as /dev/sdb , and partition /dev/sdb1 is automatically mounted. Take special care that you have accurately identified the thumb drive before proceeding. For the sake of example, we will proceed on the assumption that our thumb drive is identified as /dev/sdb , but you need to compensate accordingly. unmount any partition of the thumb drive that are automatically mounted umount /dev/sdb1 write the disk image to the thumb drive ddrescue -D --force lmde-3-201808-cinnamon-64bit.iso /dev/sdb","title":"Prepare The Installation Media"},{"location":"posts/lmde3-xfs-full-disk-encryption/#boot-the-install-disc","text":"boot into bios to disable fastboot and secureboot invoke your machine's device boot menu and boot the install disc in uefi mode confirm that you have booted in uefi mode by listing efivars ls /sys/firmware/efi/vars","title":"Boot The Install Disc"},{"location":"posts/lmde3-xfs-full-disk-encryption/#partition-the-hard-drive","text":"If you recall we are assuming the target hard drive is /dev/sda , as an example. So, make adjustments as necessary. If you would rather use a different partition tool, make sure the efi partition is an efi partition type, and you definitely need a separate /boot partition. if needed you can clear the drive with wipefs wipefs --all /dev/sda create a new partition table for /dev/sda sgdisk /dev/sda -o create a new efi partition for /dev/sda sgdisk /dev/sda --new=1::+512MiB --typecode=1:ef00 create a new /boot partition for /dev/sda sgdisk /dev/sda --new=2::+1G create a new / partition for /dev/sda sgdisk /dev/sda --new=3 verify your partition work sgdisk /dev/sda -p format the efi partition mkfs.vfat -F32 /dev/sda1 format the /boot partition mkfs.ext4 /dev/sda2 encrypt the / partition, you will be prompted for a password cryptsetup -y -v luksFormat --type luks2 /dev/sda3 decrypt the / partition, you will be prompted for a password cryptsetup open /dev/sda3 cryptroot format the / device mkfs.xfs /dev/mapper/cryptroot","title":"Partition The Hard Drive"},{"location":"posts/lmde3-xfs-full-disk-encryption/#mount-the-hard-drive","text":"This takes advantage of expert mode in the LMDE installer. create an /target directory mkdir /target mount the / device at /target mount /dev/mapper/cryptroot /target create an /target/boot directory mkdir /target/boot mount the /boot partition at /target/boot mount /dev/sda2 /target/boot create an /target/boot/efi directory mkdir /target/boot/efi mount the efi partition at /target/boot/efi mount /dev/sda1 /target/boot/efi","title":"Mount The Hard Drive"},{"location":"posts/lmde3-xfs-full-disk-encryption/#run-the-installer-app","text":"At this point you're ready to run the live installer. You can click the disc icon on the desktop. The first three pages of the live-installer cover Language,Timezone, and Keymap. The fourth page of the live-installer covers name, password, and hostname. On the fifth page of the live-installer, you come to a partition configuration page. But there is nothing to do, so select expert mode at the bottom of the page. Again select forward , and when you come to the page where you configure the location to install grub, that should be the efi partition, i.e. /dev/sda1 . Select forward one more time, and then select install. The installation will run for a few minutes and will then pause. During the pause you need to manually configure fstab and crypttab .","title":"Run The Installer App"},{"location":"posts/lmde3-xfs-full-disk-encryption/#configure-fstab","text":"find the UUID of the efi partition blkid /dev/sda1 -s UUID find the UUID of the /boot partition blkid /dev/sda2 -s UUID find the UUID of the / device blkid /dev/mapper/cryptroot -s UUID And when you find the correct UUID numbers, use them to configure /etc/fstab which is actually currently at /target/etc/fstab . # /etc/fstab ############### # efi partition # run the command `blkid /dev/sda1 -s UUID` which outputs # /dev/sda1: UUID=\"17C4-215D\", from which derive UUID=17C4-215D /boot/efi vfat defaults 0 2 # /boot partition # run the command `blkid /dev/sda2 -s UUID` which outputs # /dev/sda2: UUID=\"f2509fff-4854-4721-b546-0274c89e6aec\", from which derive UUID=f2509fff-4854-4721-b546-0274c89e6aec /boot ext4 defaults 0 2 # \"/\" device # run the command `blkid /dev/mapper/cryptroot -s UUID` which outputs # /dev/mapper/cryptroot: UUID=\"72241377-cd65-43a6-8363-1afce5bd93f6\", from which derive UUID=72241377-cd65-43a6-8363-1afce5bd93f6 / xfs defaults 0 1","title":"Configure Fstab"},{"location":"posts/lmde3-xfs-full-disk-encryption/#configure-crypttab","text":"But before the file systems can be mounted, crypttab needs to mount /dev/sda3 at /dev/mapper/cryptroot . Configure /etc/crypttab which is actually currently at /target/etc/crypttab find the UUID of the partition that will be mounted at /dev/mapper/crypttab blkid /dev/sda3 -s UUID And when you find the correct UUID number for /dev/sda3 , use that to configure /etc/crypttab which is actually currently at /target/etc/crypttab . # /etc/crypttab # run the command `blkid /dev/sda3 -s UUID` which outputs # /dev/sda3: UUID=\"da3e0967-711f-4159-85ac-7d5743a75201\", from which derive # <target name> <source device> <key file> <options> cryptroot UUID=da3e0967-711f-4159-85ac-7d5743a75201 none luks","title":"Configure Crypttab"},{"location":"posts/lmde3-xfs-full-disk-encryption/#resume-installer-app","text":"At this point finish running the live installer, and you'll be done.","title":"Resume Installer App"},{"location":"posts/lmde3-xfs-full-disk-encryption/#uefi-fix","text":"On some machines, such as HP Laptops, UEFI is broken and efi boot entries don't persist. remount the efi parition mount /dev/sda1 /mnt/ ; cd /mnt/EFI/ create a default efi executable mkdir BOOT ; cp linuxmint/grubx64.efi BOOT/BOOTX64.efi","title":"UEFI Fix"},{"location":"posts/lmde3-xfs-full-disk-encryption/#optional-swap-file","text":"Visit the Arch Wiki and they will hook you up.","title":"Optional Swap File"},{"location":"posts/lmde4-custom-partitions-disk-encryption/","text":"date: 2020-12-15 Introduction Linux Mint Debian Edition is the alternate version of Linux Mint, but built on a Debian base. The result is quite pleasant: the stability of desktop Debian, but with the rough edges polished smooth, nicely configured fonts and ui, and all the multi-media codecs included. Previously, I wrote a guide for installing LMDE3 with disk encryption . The installer for LMDE 4 is different in that it includes support for disk encryption, but not if you need custom partitions such as for a dual-boot configuration . With this in mind, the examples presented below assume that you have Windows 10 installed in 4 partitions, and thus you would want to make 3 partitions (5,6,7) after that, for LMDE4. As with before, with separate partitions for /boot formatted ext4, /boot/efi formatted fat32, and a regular luks-encrypted partition for / formatted xfs. With a separate efi partition for LMDE4, you can then use the computer's device boot menu to select which efi boot entry you want to boot. There is also an advantage in having Windows use the first efi partition, in that if something happens to the Windows efi boot entry, you can fall back to the default efi executable. Whereas, if the efi boot entry for Linux somehow gets wiped, you could repair that easily enough via chroot . Prepare The Installation Media Visit the Linux Mint Website and download the iso file for LMDE 4 64bit. Download from torrents if possible, to save bandwidth. verify the sha256 sum of the iso file sha256sum lmde-4-cinnamon-64bit.iso Identify the thumb drive you are going to install from. type lsblk , note the output, and then insert the thumb drive then type lsblk again and note the additional output # lsblk /dev/sdb NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sdb 8:32 1 14.5G 0 disk \u251c\u2500sdb1 8:33 1 3.4G 0 part /media/trent/Debian 9.6.0 amd64 \u2514\u2500sdb2 8:34 1 416K 0 part In the above example output we see that our thumb drive is identified as /dev/sdb , and partition /dev/sdb1 is automatically mounted. Take special care that you have accurately identified the thumb drive before proceeding. For the sake of example, we will proceed on the assumption that our thumb drive is identified as /dev/sdb , but you need to compensate accordingly. unmount any partition of the thumb drive that are automatically mounted umount /dev/sdb1 write the disk image to the thumb drive ddrescue -D --force lmde-4-cinnamon-64bit.iso /dev/sdb Boot The Install Disc boot into bios to disable fastboot and secureboot invoke your machine's device boot menu and boot the install disc in uefi mode confirm that you have booted in uefi mode by listing efivars ls /sys/firmware/efi/vars Partition The Hard Drive If you recall we are assuming the target hard drive is /dev/sda , as an example. So, make adjustments as necessary. If you would rather use a different partition tool, make sure the efi partition is an efi partition type, and you definitely need a separate /boot partition. If indeed, you are installing a dual-boot and are installing alongside another operating system, then skip steps 1 and 2 , obviously. if needed you can clear the drive with wipefs wipefs --all /dev/sda create a new partition table for /dev/sda sgdisk /dev/sda -o create a new efi partition for /dev/sda sgdisk /dev/sda --new=5::+512MiB --typecode=1:ef00 create a new /boot partition for /dev/sda sgdisk /dev/sda --new=6::+1G create a new / partition for /dev/sda sgdisk /dev/sda --new=7 verify your partition work sgdisk /dev/sda -p format the efi partition mkfs.vfat -F32 /dev/sda5 format the /boot partition mkfs.ext4 /dev/sda6 encrypt the / partition, you will be prompted for a password cryptsetup -y -v luksFormat --type luks2 /dev/sda7 decrypt the / partition, you will be prompted for a password cryptsetup open /dev/sda7 cryptroot format the / device mkfs.xfs /dev/mapper/cryptroot Mount The Hard Drive This takes advantage of expert mode in the LMDE installer. create an /target directory mkdir /target mount the / device at /target mount /dev/mapper/cryptroot /target create an /target/boot directory mkdir /target/boot mount the /boot partition at /target/boot mount /dev/sda6 /target/boot create an /target/boot/efi directory mkdir /target/boot/efi mount the efi partition at /target/boot/efi mount /dev/sda5 /target/boot/efi Run The Installer App From Command Line At this point you're ready to run the live installer. But you need to run the installer from the command line in order to use expert-mode : live-installer --expert-mode The first three pages of the live-installer cover Language,Timezone, and Keymap. The fourth page of the live-installer covers name, password, and hostname. After this select manual partitioning . On the seventh page of the live-installer, you come to a partition configuration page. But there is nothing to do here. The partition-configuration doesn't even recognize your encrypted partitions. But no matter, because you have already mounted the target file system relative to /target/ , so select expert mode at the bottom of the page. the installer doesn't even recognize the encrypted partitions ... ignore everything on this screen and click the `Expert mode` button Again select forward , and when you come to the page where you configure the location to install grub, that should be the efi partition, i.e. /dev/sda5 . select the efi partition as the location to install grub Then continue with the installation. The installation will run for a few minutes and will then pause. There will be a popup informing you that the installation has paused. During the pause you need to manually configure fstab and crypttab . Configure Fstab find the UUID of the efi partition blkid /dev/sda5 -s UUID find the UUID of the /boot partition blkid /dev/sda6 -s UUID find the UUID of the / device blkid /dev/mapper/cryptroot -s UUID And when you find the correct UUID numbers, use them to configure /etc/fstab which is actually currently at /target/etc/fstab . # /etc/fstab ############### # efi partition # run the command `blkid /dev/sda1 -s UUID` which outputs # /dev/sda5: UUID=\"17C4-215D\", from which derive UUID=17C4-215D /boot/efi vfat defaults 0 2 # /boot partition # run the command `blkid /dev/sda2 -s UUID` which outputs # /dev/sda6: UUID=\"f2509fff-4854-4721-b546-0274c89e6aec\", from which derive UUID=f2509fff-4854-4721-b546-0274c89e6aec /boot ext4 defaults 0 2 # \"/\" device # run the command `blkid /dev/mapper/cryptroot -s UUID` which outputs # /dev/mapper/cryptroot: UUID=\"72241377-cd65-43a6-8363-1afce5bd93f6\", from which derive UUID=72241377-cd65-43a6-8363-1afce5bd93f6 / xfs defaults 0 1 Configure Crypttab But before the file systems can be mounted, crypttab needs to mount /dev/sda3 at /dev/mapper/cryptroot . Configure /etc/crypttab which is actually currently at /target/etc/crypttab Sorry, that's actually an over-simplification. But you need to configure crypttab now, because when the installer continues running again, it installs the bootloader and builds the initramfs, and mkinitramfs parses crypttab , and builds and configures the initramfs in such a way that it knows to decrypt your / partition so it can then hand it off to the kernel at boot time (I think). find the UUID of the partition that will be mounted at /dev/mapper/crypttab blkid /dev/sda3 -s UUID And when you find the correct UUID number for /dev/sda3 , use that to configure /etc/crypttab which is actually currently at /target/etc/crypttab . # /etc/crypttab # run the command `blkid /dev/sda7 -s UUID` which outputs # /dev/sda7: UUID=\"da3e0967-711f-4159-85ac-7d5743a75201\", from which derive # <target name> <source device> <key file> <options> cryptroot UUID=da3e0967-711f-4159-85ac-7d5743a75201 none luks Resume Installer App At this point finish running the live installer, and you'll be done. UEFI Fix Well, actually there isn't one. In this scenario having two efi partitions, we rely on the motherboard correctly persisting efi boot entries. So if you are unlucky enough to have one of the HP laptops that forgets efi boot entries, I guess you are out of luck. You might try using a single efi partition instead of two, and maybe that will work. Presumably this would require using VeraCrypt for Windows, instead of Bitlocker (because Bitlocker won't allow Grub to load the Windows bootloader?) Optional Swap File Visit the Arch Wiki and they will hook you up.","title":"LMDE4 Custom Partitions for Disk Encryption"},{"location":"posts/lmde4-custom-partitions-disk-encryption/#introduction","text":"Linux Mint Debian Edition is the alternate version of Linux Mint, but built on a Debian base. The result is quite pleasant: the stability of desktop Debian, but with the rough edges polished smooth, nicely configured fonts and ui, and all the multi-media codecs included. Previously, I wrote a guide for installing LMDE3 with disk encryption . The installer for LMDE 4 is different in that it includes support for disk encryption, but not if you need custom partitions such as for a dual-boot configuration . With this in mind, the examples presented below assume that you have Windows 10 installed in 4 partitions, and thus you would want to make 3 partitions (5,6,7) after that, for LMDE4. As with before, with separate partitions for /boot formatted ext4, /boot/efi formatted fat32, and a regular luks-encrypted partition for / formatted xfs. With a separate efi partition for LMDE4, you can then use the computer's device boot menu to select which efi boot entry you want to boot. There is also an advantage in having Windows use the first efi partition, in that if something happens to the Windows efi boot entry, you can fall back to the default efi executable. Whereas, if the efi boot entry for Linux somehow gets wiped, you could repair that easily enough via chroot .","title":"Introduction"},{"location":"posts/lmde4-custom-partitions-disk-encryption/#prepare-the-installation-media","text":"Visit the Linux Mint Website and download the iso file for LMDE 4 64bit. Download from torrents if possible, to save bandwidth. verify the sha256 sum of the iso file sha256sum lmde-4-cinnamon-64bit.iso Identify the thumb drive you are going to install from. type lsblk , note the output, and then insert the thumb drive then type lsblk again and note the additional output # lsblk /dev/sdb NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sdb 8:32 1 14.5G 0 disk \u251c\u2500sdb1 8:33 1 3.4G 0 part /media/trent/Debian 9.6.0 amd64 \u2514\u2500sdb2 8:34 1 416K 0 part In the above example output we see that our thumb drive is identified as /dev/sdb , and partition /dev/sdb1 is automatically mounted. Take special care that you have accurately identified the thumb drive before proceeding. For the sake of example, we will proceed on the assumption that our thumb drive is identified as /dev/sdb , but you need to compensate accordingly. unmount any partition of the thumb drive that are automatically mounted umount /dev/sdb1 write the disk image to the thumb drive ddrescue -D --force lmde-4-cinnamon-64bit.iso /dev/sdb","title":"Prepare The Installation Media"},{"location":"posts/lmde4-custom-partitions-disk-encryption/#boot-the-install-disc","text":"boot into bios to disable fastboot and secureboot invoke your machine's device boot menu and boot the install disc in uefi mode confirm that you have booted in uefi mode by listing efivars ls /sys/firmware/efi/vars","title":"Boot The Install Disc"},{"location":"posts/lmde4-custom-partitions-disk-encryption/#partition-the-hard-drive","text":"If you recall we are assuming the target hard drive is /dev/sda , as an example. So, make adjustments as necessary. If you would rather use a different partition tool, make sure the efi partition is an efi partition type, and you definitely need a separate /boot partition. If indeed, you are installing a dual-boot and are installing alongside another operating system, then skip steps 1 and 2 , obviously. if needed you can clear the drive with wipefs wipefs --all /dev/sda create a new partition table for /dev/sda sgdisk /dev/sda -o create a new efi partition for /dev/sda sgdisk /dev/sda --new=5::+512MiB --typecode=1:ef00 create a new /boot partition for /dev/sda sgdisk /dev/sda --new=6::+1G create a new / partition for /dev/sda sgdisk /dev/sda --new=7 verify your partition work sgdisk /dev/sda -p format the efi partition mkfs.vfat -F32 /dev/sda5 format the /boot partition mkfs.ext4 /dev/sda6 encrypt the / partition, you will be prompted for a password cryptsetup -y -v luksFormat --type luks2 /dev/sda7 decrypt the / partition, you will be prompted for a password cryptsetup open /dev/sda7 cryptroot format the / device mkfs.xfs /dev/mapper/cryptroot","title":"Partition The Hard Drive"},{"location":"posts/lmde4-custom-partitions-disk-encryption/#mount-the-hard-drive","text":"This takes advantage of expert mode in the LMDE installer. create an /target directory mkdir /target mount the / device at /target mount /dev/mapper/cryptroot /target create an /target/boot directory mkdir /target/boot mount the /boot partition at /target/boot mount /dev/sda6 /target/boot create an /target/boot/efi directory mkdir /target/boot/efi mount the efi partition at /target/boot/efi mount /dev/sda5 /target/boot/efi","title":"Mount The Hard Drive"},{"location":"posts/lmde4-custom-partitions-disk-encryption/#run-the-installer-app-from-command-line","text":"At this point you're ready to run the live installer. But you need to run the installer from the command line in order to use expert-mode : live-installer --expert-mode The first three pages of the live-installer cover Language,Timezone, and Keymap. The fourth page of the live-installer covers name, password, and hostname. After this select manual partitioning . On the seventh page of the live-installer, you come to a partition configuration page. But there is nothing to do here. The partition-configuration doesn't even recognize your encrypted partitions. But no matter, because you have already mounted the target file system relative to /target/ , so select expert mode at the bottom of the page. the installer doesn't even recognize the encrypted partitions ... ignore everything on this screen and click the `Expert mode` button Again select forward , and when you come to the page where you configure the location to install grub, that should be the efi partition, i.e. /dev/sda5 . select the efi partition as the location to install grub Then continue with the installation. The installation will run for a few minutes and will then pause. There will be a popup informing you that the installation has paused. During the pause you need to manually configure fstab and crypttab .","title":"Run The Installer App From Command Line"},{"location":"posts/lmde4-custom-partitions-disk-encryption/#configure-fstab","text":"find the UUID of the efi partition blkid /dev/sda5 -s UUID find the UUID of the /boot partition blkid /dev/sda6 -s UUID find the UUID of the / device blkid /dev/mapper/cryptroot -s UUID And when you find the correct UUID numbers, use them to configure /etc/fstab which is actually currently at /target/etc/fstab . # /etc/fstab ############### # efi partition # run the command `blkid /dev/sda1 -s UUID` which outputs # /dev/sda5: UUID=\"17C4-215D\", from which derive UUID=17C4-215D /boot/efi vfat defaults 0 2 # /boot partition # run the command `blkid /dev/sda2 -s UUID` which outputs # /dev/sda6: UUID=\"f2509fff-4854-4721-b546-0274c89e6aec\", from which derive UUID=f2509fff-4854-4721-b546-0274c89e6aec /boot ext4 defaults 0 2 # \"/\" device # run the command `blkid /dev/mapper/cryptroot -s UUID` which outputs # /dev/mapper/cryptroot: UUID=\"72241377-cd65-43a6-8363-1afce5bd93f6\", from which derive UUID=72241377-cd65-43a6-8363-1afce5bd93f6 / xfs defaults 0 1","title":"Configure Fstab"},{"location":"posts/lmde4-custom-partitions-disk-encryption/#configure-crypttab","text":"But before the file systems can be mounted, crypttab needs to mount /dev/sda3 at /dev/mapper/cryptroot . Configure /etc/crypttab which is actually currently at /target/etc/crypttab Sorry, that's actually an over-simplification. But you need to configure crypttab now, because when the installer continues running again, it installs the bootloader and builds the initramfs, and mkinitramfs parses crypttab , and builds and configures the initramfs in such a way that it knows to decrypt your / partition so it can then hand it off to the kernel at boot time (I think). find the UUID of the partition that will be mounted at /dev/mapper/crypttab blkid /dev/sda3 -s UUID And when you find the correct UUID number for /dev/sda3 , use that to configure /etc/crypttab which is actually currently at /target/etc/crypttab . # /etc/crypttab # run the command `blkid /dev/sda7 -s UUID` which outputs # /dev/sda7: UUID=\"da3e0967-711f-4159-85ac-7d5743a75201\", from which derive # <target name> <source device> <key file> <options> cryptroot UUID=da3e0967-711f-4159-85ac-7d5743a75201 none luks","title":"Configure Crypttab"},{"location":"posts/lmde4-custom-partitions-disk-encryption/#resume-installer-app","text":"At this point finish running the live installer, and you'll be done.","title":"Resume Installer App"},{"location":"posts/lmde4-custom-partitions-disk-encryption/#uefi-fix","text":"Well, actually there isn't one. In this scenario having two efi partitions, we rely on the motherboard correctly persisting efi boot entries. So if you are unlucky enough to have one of the HP laptops that forgets efi boot entries, I guess you are out of luck. You might try using a single efi partition instead of two, and maybe that will work. Presumably this would require using VeraCrypt for Windows, instead of Bitlocker (because Bitlocker won't allow Grub to load the Windows bootloader?)","title":"UEFI Fix"},{"location":"posts/lmde4-custom-partitions-disk-encryption/#optional-swap-file","text":"Visit the Arch Wiki and they will hook you up.","title":"Optional Swap File"},{"location":"posts/prosody-photo-uploads/","text":"date: 2021-01-25 Introduction Install prosody on Debian 10 with photoupload, postgresql database, and letsencrypt certs. DNS Log into your dns provider and create A and AAAA records for xmpp.example.com Log into your dns provider and create A and AAAA records for xmppupload.example.com FireWall Incidentally, you definitely do want to use a non-standard ssh port for connecting over the internet. I would suggest that a firewall is important, because I couldn't figure out how to completely disable port 5280 for the http protocol, in the clear, in the prosody config. ports 80/tcp , 443/tcp for certbot 4444/tcp i.e. port 4444 for ssh 5222/tcp for xmpp-client 5269/tcp for xmpp-server 5281/tcp for https connections to prosody for uploads and photos FireWall with UFW ufw allow http ufw allow https ufw allow xmpp-client ufw allow xmpp-server ufw allow 5281/tcp ufw allow 4444/tcp i.e. if 4444 for ssh ufw enable to start the firewall Postgresql Database Install the postgresql database. apt-get install postgresql postgresql-contrib Log into the psql command line. sudo -u postgres psql Create prosody database postgres =# CREATE DATABASE prosody ; Creat prosody user postgres =# CREATE ROLE prosody WITH LOGIN ; Set password for user postgres =# \\ password prosody Quit psql postgres =# \\ q allow authentication in pg_hba.conf To connect to postgresql via unix socket # /etc/postgresql/11/main/pg_hba.conf # make sure this line is above local prosody prosody md5 # make sure this line is below local all all peer or i.e. through a wireguard tunnel # /etc/postgresql/11/main/pg_hba.conf # where 10.0.22.5 is the ip address of the machine that prosody will run on host prosody prosody 10.0.22.5/32 md5 and then restart postgresql systemctl restart postgresql Prosody Install Prosody apt install prosody prosody-modules lua-dbi-postgresql Configure Prosody backup the prosody config file cp /etc/prosody/prosody.cfg.lua /etc/prosody/prosody.cfg.lua.bak if you want to disable advertising version and uptime, allow message archives, and disallow registration, change this -- /etc/prosody/prosody.cfg.lua modules_enabled = { ... -- Nice to have \"version\"; -- Replies to server version requests \"uptime\"; -- Report how long server has been running \"time\"; -- Let others know the time here on this server \"ping\"; -- Replies to XMPP pings with pongs \"register\"; -- Allow users to register on this server using a client and change passwords --\"mam\"; -- Store messages in an archive and allow users to access it --\"csi_simple\"; -- Simple Mobile optimizations ... } to this -- /etc/prosody/prosody.cfg.lua modules_enabled = { ... -- Nice to have --\"version\"; -- Replies to server version requests --\"uptime\"; -- Report how long server has been running \"time\"; -- Let others know the time here on this server \"ping\"; -- Replies to XMPP pings with pongs --\"register\"; -- Allow users to register on this server using a client and change passwords \"mam\"; -- Store messages in an archive and allow users to access it --\"csi_simple\"; -- Simple Mobile optimizations ... } to force certificate authentication for server-to-server connections, make the following edit around line 123 -- /etc/prosody/prosody.cfg.lua -- Force certificate authentication for server-to-server connections? -- change this s2s_secure_auth = false -- to this s2s_secure_auth = true around line 147 enable sql -- /etc/prosody/prosody.cfg.lua -- change this --storage = \"sql\" -- to this storage = \"sql\" and describe the database connection -- /etc/prosody/prosody.cfg.lua -- change this --sql = { driver = \"PostgreSQL\", database = \"prosody\", username = \"prosody\", password = \"secret\", host = \"localhost\" } -- to this sql = { driver = \"PostgreSQL\", database = \"prosody\", username = \"prosody\", password = \"secret\", host = \"localhost\" } -- or to use a unix socket in Debian 10 sql = { driver = \"PostgreSQL\", database = \"prosody\", username = \"prosody\", password = \"secret\", host = \"/var/run/postgresql\" } somewhere around line 196, describe the certificate file for the upoad subdomain -- /etc/prosody/prosody.cfg.lua -- change this --https_certificate = \"/etc/prosody/certs/localhost.crt\" -- to this https_certificate = \"/etc/prosody/certs/xmppupload.example.com.crt\" somewhere around line 210 describe your virtualhost -- /etc/prosody/prosody.cfg.lua VirtualHost \"xmpp.example.com\" disco_items = { {\"xmppupload.example.com\"}, } add the following to the end of the file -- /etc/prosody/prosody.cfg.lua Component \"xmppupload.example.com\" \"http_upload\" and then restart prosody systemctl restart prososdy Certbot install certbot apt install certbot get certificates certbot certonly -d xmpp.example.com certbot certonly -d xmppupload.example.com import the certificates into prosody and restart prosody prosodyctl --root cert import /etc/letsencrypt/live systemctl restart prosody create the following renewal-hook for letsencrypt # !/bin/bash # /etc/letsencrypt/renewal-hooks/deploy/prosody_deploy_hook prosodyctl --root cert import /etc/letsencrypt/live","title":"Prosody Photo Uploads"},{"location":"posts/prosody-photo-uploads/#introduction","text":"Install prosody on Debian 10 with photoupload, postgresql database, and letsencrypt certs.","title":"Introduction"},{"location":"posts/prosody-photo-uploads/#dns","text":"Log into your dns provider and create A and AAAA records for xmpp.example.com Log into your dns provider and create A and AAAA records for xmppupload.example.com","title":"DNS"},{"location":"posts/prosody-photo-uploads/#firewall","text":"Incidentally, you definitely do want to use a non-standard ssh port for connecting over the internet. I would suggest that a firewall is important, because I couldn't figure out how to completely disable port 5280 for the http protocol, in the clear, in the prosody config.","title":"FireWall"},{"location":"posts/prosody-photo-uploads/#ports","text":"80/tcp , 443/tcp for certbot 4444/tcp i.e. port 4444 for ssh 5222/tcp for xmpp-client 5269/tcp for xmpp-server 5281/tcp for https connections to prosody for uploads and photos","title":"ports"},{"location":"posts/prosody-photo-uploads/#firewall-with-ufw","text":"ufw allow http ufw allow https ufw allow xmpp-client ufw allow xmpp-server ufw allow 5281/tcp ufw allow 4444/tcp i.e. if 4444 for ssh ufw enable to start the firewall","title":"FireWall with UFW"},{"location":"posts/prosody-photo-uploads/#postgresql-database","text":"","title":"Postgresql Database"},{"location":"posts/prosody-photo-uploads/#install-the-postgresql-database","text":"apt-get install postgresql postgresql-contrib Log into the psql command line. sudo -u postgres psql Create prosody database postgres =# CREATE DATABASE prosody ; Creat prosody user postgres =# CREATE ROLE prosody WITH LOGIN ; Set password for user postgres =# \\ password prosody Quit psql postgres =# \\ q","title":"Install the postgresql database."},{"location":"posts/prosody-photo-uploads/#allow-authentication-in-pg_hbaconf","text":"To connect to postgresql via unix socket # /etc/postgresql/11/main/pg_hba.conf # make sure this line is above local prosody prosody md5 # make sure this line is below local all all peer or i.e. through a wireguard tunnel # /etc/postgresql/11/main/pg_hba.conf # where 10.0.22.5 is the ip address of the machine that prosody will run on host prosody prosody 10.0.22.5/32 md5 and then restart postgresql systemctl restart postgresql","title":"allow authentication in pg_hba.conf"},{"location":"posts/prosody-photo-uploads/#prosody","text":"","title":"Prosody"},{"location":"posts/prosody-photo-uploads/#install-prosody","text":"apt install prosody prosody-modules lua-dbi-postgresql","title":"Install Prosody"},{"location":"posts/prosody-photo-uploads/#configure-prosody","text":"backup the prosody config file cp /etc/prosody/prosody.cfg.lua /etc/prosody/prosody.cfg.lua.bak if you want to disable advertising version and uptime, allow message archives, and disallow registration, change this -- /etc/prosody/prosody.cfg.lua modules_enabled = { ... -- Nice to have \"version\"; -- Replies to server version requests \"uptime\"; -- Report how long server has been running \"time\"; -- Let others know the time here on this server \"ping\"; -- Replies to XMPP pings with pongs \"register\"; -- Allow users to register on this server using a client and change passwords --\"mam\"; -- Store messages in an archive and allow users to access it --\"csi_simple\"; -- Simple Mobile optimizations ... } to this -- /etc/prosody/prosody.cfg.lua modules_enabled = { ... -- Nice to have --\"version\"; -- Replies to server version requests --\"uptime\"; -- Report how long server has been running \"time\"; -- Let others know the time here on this server \"ping\"; -- Replies to XMPP pings with pongs --\"register\"; -- Allow users to register on this server using a client and change passwords \"mam\"; -- Store messages in an archive and allow users to access it --\"csi_simple\"; -- Simple Mobile optimizations ... } to force certificate authentication for server-to-server connections, make the following edit around line 123 -- /etc/prosody/prosody.cfg.lua -- Force certificate authentication for server-to-server connections? -- change this s2s_secure_auth = false -- to this s2s_secure_auth = true around line 147 enable sql -- /etc/prosody/prosody.cfg.lua -- change this --storage = \"sql\" -- to this storage = \"sql\" and describe the database connection -- /etc/prosody/prosody.cfg.lua -- change this --sql = { driver = \"PostgreSQL\", database = \"prosody\", username = \"prosody\", password = \"secret\", host = \"localhost\" } -- to this sql = { driver = \"PostgreSQL\", database = \"prosody\", username = \"prosody\", password = \"secret\", host = \"localhost\" } -- or to use a unix socket in Debian 10 sql = { driver = \"PostgreSQL\", database = \"prosody\", username = \"prosody\", password = \"secret\", host = \"/var/run/postgresql\" } somewhere around line 196, describe the certificate file for the upoad subdomain -- /etc/prosody/prosody.cfg.lua -- change this --https_certificate = \"/etc/prosody/certs/localhost.crt\" -- to this https_certificate = \"/etc/prosody/certs/xmppupload.example.com.crt\" somewhere around line 210 describe your virtualhost -- /etc/prosody/prosody.cfg.lua VirtualHost \"xmpp.example.com\" disco_items = { {\"xmppupload.example.com\"}, } add the following to the end of the file -- /etc/prosody/prosody.cfg.lua Component \"xmppupload.example.com\" \"http_upload\" and then restart prosody systemctl restart prososdy","title":"Configure Prosody"},{"location":"posts/prosody-photo-uploads/#certbot","text":"install certbot apt install certbot get certificates certbot certonly -d xmpp.example.com certbot certonly -d xmppupload.example.com import the certificates into prosody and restart prosody prosodyctl --root cert import /etc/letsencrypt/live systemctl restart prosody create the following renewal-hook for letsencrypt # !/bin/bash # /etc/letsencrypt/renewal-hooks/deploy/prosody_deploy_hook prosodyctl --root cert import /etc/letsencrypt/live","title":"Certbot"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/","text":"date: 2021-10-06 Introduction Apparently Windows has a problem resolving hosts when you tether from Mobile HotSpot. The solution is to build a DNS-Caching router that tethers off the smartphone. This takes advantage of Android's ability so transparently fail-over to LTE when residential internet service goes down. This solution also takes advantage of the RaspberryPi's incredibly low price, the fact that LineageOS will run on cheap old phones that are no longer supported by the mfgr, and the fact that GoogleFi will ship you a data-sim for free. For instance, I just bought a brand-new, open-box Pixel phone for $85, and presumable the MotoX4 can also be had for next to nothing. Materials RaspberryPi running Arch Linux Arm Old Android Phone Running LineageOS Free Data Sim Card From GoogleFi RaspberryPi Router tethered off MotoX4 (running LineageOS) Setup Personally I would secure the ssh server generate and configure the locale remove the default root password, and default user write your preferred hostname in /etc/hostname configure your preferred timezone: ln -sf /usr/share/zoneinfo/<Zone>/<SubZone> /etc/localtime Additionally, the router won't be accessible for administrative tasks when it is behind the Android Tether ; for this I would use a wireguard vpn . Configure The Router. The entire configuration of the router consists of two systemd-networkd interface definitions, as well as /etc/resolv.conf , and /etc/dnsmasq.conf . resolvconf systemd-resolved is no use to us because it only listens on localhost. # disable systemd-resolved systemctl stop systemd-resolved systemctl disable systemd-resolved unlink /etc/resolv.conf After unlinking the symlinked version of /etc/resolv.conf , write your nameservers and options in a real /etc/resolv.conf . # the default timeout of 5 seconds is too slow options timeout:1 # nameserver when connected to lan nameserver 192.168.1.1 # nameserver when connected to mobile network nameserver 8.8.8.8 Interface Definitions For systemd-networkd I believe the usb interfaces are numbered 1-4, so either be careful which one you use, or maybe a wildcard name will work, i.e. Name=usb* # uplink # /etc/systemd/network/usb0.network [Match] Name = usb0 [Network] DHCP = yes DNSSEC = no IPForward = yes # downlink, ethernet cable # /etc/systemd/network/eth0.network [Match] Name = eth0 [Network] Address = 10.12.34.1/24 DHCPServer = yes IPForward = yes IPMasquerade = both Configuration For dnsmasq Install dnsmasq , and enable it systemctl enable dnsmasq . # /etc/dnsmasq.conf resolv-file = /etc/resolv.conf interface = eth0 no-dhcp-interface = eth0 Reboot Plug in the Android Phone, reboot the RaspberryPi, and when it comes back up toggle on the USB tether on the Android Phone. Plug ethernet cable into Windows Computer, open CMD prompt and type ping google.com to test connectivity and name resolution. Or on a Linux computer type ping -c 3 google.com . Alternate DHCP Service You can use dnsmasq for DHCP Service instead of systemd-networkd . # downlink, ethernet cable # /etc/systemd/network/eth0.network [Match] Name = eth0 [Network] Address = 10.12.34.1/24 # DHCPServer=yes IPForward = yes IPMasquerade = both # /etc/dnsmasq.conf resolv-file = /etc/resolv.conf interface = eth0 # no-dhcp-interface=eth0 dhcp-range = 10.12.34.50,10.12.34.150 Reference For systemd-networkd examples in /usr/lib/systemd/network/ Man Page Use With Multiple Computers Just add an unmanaged switch . Wifi Instead of Ethernet Use downlink definition for wlan0 instead of eth0 , and install hostapd # /etc/hostapd/hostapd.conf interface = wlan0 hw_mode = g channel = 7 wmm_enabled = 0 macaddr_acl = 0 auth_algs = 1 ignore_broadcast_ssid = 0 wpa = 2 wpa_key_mgmt = WPA-PSK wpa_pairwise = TKIP rsn_pairwise = CCMP ssid = NETWORK wpa_passphrase = PASSWORD","title":"RaspberryPi LTE-Failover Router With DNS Caching"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#introduction","text":"Apparently Windows has a problem resolving hosts when you tether from Mobile HotSpot. The solution is to build a DNS-Caching router that tethers off the smartphone. This takes advantage of Android's ability so transparently fail-over to LTE when residential internet service goes down. This solution also takes advantage of the RaspberryPi's incredibly low price, the fact that LineageOS will run on cheap old phones that are no longer supported by the mfgr, and the fact that GoogleFi will ship you a data-sim for free. For instance, I just bought a brand-new, open-box Pixel phone for $85, and presumable the MotoX4 can also be had for next to nothing.","title":"Introduction"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#materials","text":"RaspberryPi running Arch Linux Arm Old Android Phone Running LineageOS Free Data Sim Card From GoogleFi RaspberryPi Router tethered off MotoX4 (running LineageOS)","title":"Materials"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#setup","text":"Personally I would secure the ssh server generate and configure the locale remove the default root password, and default user write your preferred hostname in /etc/hostname configure your preferred timezone: ln -sf /usr/share/zoneinfo/<Zone>/<SubZone> /etc/localtime Additionally, the router won't be accessible for administrative tasks when it is behind the Android Tether ; for this I would use a wireguard vpn .","title":"Setup"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#configure-the-router","text":"The entire configuration of the router consists of two systemd-networkd interface definitions, as well as /etc/resolv.conf , and /etc/dnsmasq.conf .","title":"Configure The Router."},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#resolvconf","text":"systemd-resolved is no use to us because it only listens on localhost. # disable systemd-resolved systemctl stop systemd-resolved systemctl disable systemd-resolved unlink /etc/resolv.conf After unlinking the symlinked version of /etc/resolv.conf , write your nameservers and options in a real /etc/resolv.conf . # the default timeout of 5 seconds is too slow options timeout:1 # nameserver when connected to lan nameserver 192.168.1.1 # nameserver when connected to mobile network nameserver 8.8.8.8","title":"resolvconf"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#interface-definitions-for-systemd-networkd","text":"I believe the usb interfaces are numbered 1-4, so either be careful which one you use, or maybe a wildcard name will work, i.e. Name=usb* # uplink # /etc/systemd/network/usb0.network [Match] Name = usb0 [Network] DHCP = yes DNSSEC = no IPForward = yes # downlink, ethernet cable # /etc/systemd/network/eth0.network [Match] Name = eth0 [Network] Address = 10.12.34.1/24 DHCPServer = yes IPForward = yes IPMasquerade = both","title":"Interface Definitions For systemd-networkd"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#configuration-for-dnsmasq","text":"Install dnsmasq , and enable it systemctl enable dnsmasq . # /etc/dnsmasq.conf resolv-file = /etc/resolv.conf interface = eth0 no-dhcp-interface = eth0","title":"Configuration For dnsmasq"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#reboot","text":"Plug in the Android Phone, reboot the RaspberryPi, and when it comes back up toggle on the USB tether on the Android Phone. Plug ethernet cable into Windows Computer, open CMD prompt and type ping google.com to test connectivity and name resolution. Or on a Linux computer type ping -c 3 google.com .","title":"Reboot"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#alternate-dhcp-service","text":"You can use dnsmasq for DHCP Service instead of systemd-networkd . # downlink, ethernet cable # /etc/systemd/network/eth0.network [Match] Name = eth0 [Network] Address = 10.12.34.1/24 # DHCPServer=yes IPForward = yes IPMasquerade = both # /etc/dnsmasq.conf resolv-file = /etc/resolv.conf interface = eth0 # no-dhcp-interface=eth0 dhcp-range = 10.12.34.50,10.12.34.150","title":"Alternate DHCP Service"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#reference-for-systemd-networkd","text":"examples in /usr/lib/systemd/network/ Man Page","title":"Reference For systemd-networkd"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#use-with-multiple-computers","text":"Just add an unmanaged switch .","title":"Use With Multiple Computers"},{"location":"posts/raspberrypi-lte-failover-router-with-dns-caching/#wifi-instead-of-ethernet","text":"Use downlink definition for wlan0 instead of eth0 , and install hostapd # /etc/hostapd/hostapd.conf interface = wlan0 hw_mode = g channel = 7 wmm_enabled = 0 macaddr_acl = 0 auth_algs = 1 ignore_broadcast_ssid = 0 wpa = 2 wpa_key_mgmt = WPA-PSK wpa_pairwise = TKIP rsn_pairwise = CCMP ssid = NETWORK wpa_passphrase = PASSWORD","title":"Wifi Instead of Ethernet"},{"location":"posts/rewrite-hugo-themes-report-in-python/","text":"date: 2019-01-25T01:02:57-08:00 Ranking Hugo Themes by Stars, Commit Date A while back I was grazing the selfhosted subreddit, and noticed Hugo coming up in conversation. I recalled that hugo requires a third-party theme in order to function. But was a bit of a challenge, because how do you know what is a good Hugo theme? First Version in Bash I ended up writing a little bash script (now deprecated) that scrapes the Github api and generates a little report about Hugo themes. It basically curled json from the Github api, and parsed it with grep, awk, and sed, and eventually spat out a plain text file. Rewrite in Python It was about a year later that I decided to rewrite the script in Python, using sqlite as a database. I discovered how to use the python requests module, got some practice with sqlite, and discovered how to make conditional request against the Github api using ETags and \u2018If-Modified-Since\u2019 (ETags are easier). But this was my first time using python like this. And I have to tell you, it\u2019s a lot moar fun than recursive fibonacci tutorials! Building an HTML5 Table (bootstrap, actually) By the time I had figured out how to collect the data I needed, I realized that I could simply generate an html table right in the python script. rank_hugo_themes.py runs in a cronjob every night, and you can view Hugo Themes Report here. And you can see the script on Github .","title":"Rewrite Hugo Themes Report In Python"},{"location":"posts/rewrite-hugo-themes-report-in-python/#ranking-hugo-themes-by-stars-commit-date","text":"A while back I was grazing the selfhosted subreddit, and noticed Hugo coming up in conversation. I recalled that hugo requires a third-party theme in order to function. But was a bit of a challenge, because how do you know what is a good Hugo theme?","title":"Ranking Hugo Themes by Stars, Commit Date"},{"location":"posts/rewrite-hugo-themes-report-in-python/#first-version-in-bash","text":"I ended up writing a little bash script (now deprecated) that scrapes the Github api and generates a little report about Hugo themes. It basically curled json from the Github api, and parsed it with grep, awk, and sed, and eventually spat out a plain text file.","title":"First Version in Bash"},{"location":"posts/rewrite-hugo-themes-report-in-python/#rewrite-in-python","text":"It was about a year later that I decided to rewrite the script in Python, using sqlite as a database. I discovered how to use the python requests module, got some practice with sqlite, and discovered how to make conditional request against the Github api using ETags and \u2018If-Modified-Since\u2019 (ETags are easier). But this was my first time using python like this. And I have to tell you, it\u2019s a lot moar fun than recursive fibonacci tutorials!","title":"Rewrite in Python"},{"location":"posts/rewrite-hugo-themes-report-in-python/#building-an-html5-table-bootstrap-actually","text":"By the time I had figured out how to collect the data I needed, I realized that I could simply generate an html table right in the python script. rank_hugo_themes.py runs in a cronjob every night, and you can view Hugo Themes Report here. And you can see the script on Github .","title":"Building an HTML5 Table (bootstrap, actually)"},{"location":"posts/sendxmpp-handler-for-python-logging/","text":"date: 2020-12-19 SENDXMPPHandler for Python Logging app/__init__.py You may be familiar with adding a logging handler to a flask application, with something like the following in __init__.py . app/sendxmpp_handler.py python-logging doesn't have a handler for xmpp but the handlers that are available are easy enough to understand if you read through them in handlers.py . Using the available handlers as an example, it did not require a lot of imagination to come up with SENDXMPPHandler. Android Yaxim Screenshot And this is what a flask logging error looks like on Android, in Yaxim.","title":"SENDXMPP Handler for Python Logging"},{"location":"posts/sendxmpp-handler-for-python-logging/#sendxmpphandler-for-python-logging","text":"","title":"SENDXMPPHandler for Python Logging"},{"location":"posts/sendxmpp-handler-for-python-logging/#app__init__py","text":"You may be familiar with adding a logging handler to a flask application, with something like the following in __init__.py .","title":"app/__init__.py"},{"location":"posts/sendxmpp-handler-for-python-logging/#appsendxmpp_handlerpy","text":"python-logging doesn't have a handler for xmpp but the handlers that are available are easy enough to understand if you read through them in handlers.py . Using the available handlers as an example, it did not require a lot of imagination to come up with SENDXMPPHandler.","title":"app/sendxmpp_handler.py"},{"location":"posts/sendxmpp-handler-for-python-logging/#android-yaxim-screenshot","text":"And this is what a flask logging error looks like on Android, in Yaxim.","title":"Android Yaxim Screenshot"},{"location":"posts/simplified-raspberry-streaming/","text":"date: 2019-05-12T18:32:55-07:00 RaspberryPi is a Great MPD Appliance I\u2019m really pleased with the RaspberryPi as an MPD (music player daemon), appliance. I have it hooked up to the home surround-sound system via spdif, digital optical cable hat, btw, running Arch Linux ARM , with the / file system on a dual-thumbdrive, btrfs raid1 (mirror) device . It plays music around the clock, reliably, without breaking a sweat. And the mpd daemon is easy to remote control, either from the command line with ncmpcpp , or using M.A.L.P for Android . And/Or as an Internet Radio Streaming Client The beauty of this setup it in the simplicity. All you have to do is create an plain text *m3u file with the address:port of the internet radio stream you want, and place that in /var/lib/mpd/playlists directory. You can find various internet radio lists on the internet, and many offer example *m3u playlist files that you can download. However, the important thing is that your m3u playlist file has to contain the exact streaming address, so if the m3u file you download points to a pls file, you may have to download that pls file to look for the streaming address.","title":"Simplified Raspberry Streaming"},{"location":"posts/simplified-raspberry-streaming/#raspberrypi-is-a-great-mpd-appliance","text":"I\u2019m really pleased with the RaspberryPi as an MPD (music player daemon), appliance. I have it hooked up to the home surround-sound system via spdif, digital optical cable hat, btw, running Arch Linux ARM , with the / file system on a dual-thumbdrive, btrfs raid1 (mirror) device . It plays music around the clock, reliably, without breaking a sweat. And the mpd daemon is easy to remote control, either from the command line with ncmpcpp , or using M.A.L.P for Android .","title":"RaspberryPi is a Great MPD Appliance"},{"location":"posts/simplified-raspberry-streaming/#andor-as-an-internet-radio-streaming-client","text":"The beauty of this setup it in the simplicity. All you have to do is create an plain text *m3u file with the address:port of the internet radio stream you want, and place that in /var/lib/mpd/playlists directory. You can find various internet radio lists on the internet, and many offer example *m3u playlist files that you can download. However, the important thing is that your m3u playlist file has to contain the exact streaming address, so if the m3u file you download points to a pls file, you may have to download that pls file to look for the streaming address.","title":"And/Or as an Internet Radio Streaming Client"},{"location":"posts/test-qr-svg-django/","text":"date: 2021-04-19 Introduction I worked out a solution in django-testing, for testing a view that renders a qrcode as an svg as an inline svg xml string. In case you are not familiar with svg , scalable vector graphics, it is an image that is completely rendered from a string of text, or more accurately XML text, unlike PNG or JPG , which are binary file s. Python Libraries Used BeautifulSoup to consume html CairoSVG to convert svg to png python-pillow to convert transparent png background to white opencv-python to extract data from qrcode python-pyotp Form ScreenShot This is the form we will be testing. In real life you would confirm that you want to use two-factor-authentication by scanning the qrcode with an authentication app on your smartphone, and then enter the time-based one-time password . totp codes are derived from two things and two things only. A secret key and a time such as the current time. For instance, an authentication application can tell you what your totp code is, but of course in this testing scenario we use python-pyotp , after extracting the secret key from the qrcode. enable totp confirmation form Import Python Libraries # tp/accounts/tests/test_enable_totp_view.py # python manage.py test accounts.tests.test_enable_totp_view from django.test import TestCase from django.contrib.auth.models import User from accounts.models import Account from django.urls import reverse from bs4 import BeautifulSoup import cv2 from cairosvg import svg2png from PIL import Image import pyotp import pathlib The Account model has a one-to-one relationship with the Django built-in User model. This is where we keep track of the totp secret key and boolean value for having totp authentication enabled. setUp TestCase ... import pathlib class TestEnableTOTPViewTestCase ( TestCase ): def setUp ( self ): user_a = User . objects . create ( username = 'user_a' ) user_a . set_password ( 'password_user_a' ) user_a . save () Account . objects . create ( user = user_a ) We create a test user and save that in the test database. GET Form class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): self . client . login ( username = 'user_a' , password = 'password_user_a' ) get_response = self . client . get ( reverse ( 'accounts:enable_totp' ) ) self . assertEquals ( get_response . status_code , 200 ) self . assertTemplateUsed ( get_response , 'accounts/totp_form.html' ) self . assertEquals ( get_response . request [ 'PATH_INFO' ], '/accounts/enable-totp/' ) The TestCase requires two requests. In the first request we GET the form so that we can consume the qrcode. Consume SVG with BeautifulSoup class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): ... soup = BeautifulSoup ( get_response . content , features = \"lxml\" ) svg_container = soup . find ( \"div\" , { \"id\" : \"svgcontainer\" } ) self . assertIsNotNone ( svg_container ) scsvg = svg_container . findChild ( \"svg\" , recursive = False ) self . assertIsNotNone ( scsvg ) with open ( 'qr.svg' , 'w' ) as f : x_string = \"<?xml version='1.0'\" x_string += \" encoding='utf-8'?> \\n \" f . write ( x_string + str ( scsvg )) The inline xml for the svg comes to us as a child of a div with an id of svgcontainer . We capture the xml of the svg in the variable scsvg , and then write it out to disc. svg2png class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): ... svg2png ( url = 'qr.svg' , write_to = 'qr.png' , scale = 8 ) With svg2png from CairoSVG, we convert the svg to png format. Opencv seems unable to consume the qrcode unless you scale it up. Add White Background class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): ... t_image = Image . open ( 'qr.png' ) t_image . load () background = Image . new ( \"RGB\" , t_image . size , ( 255 , 255 , 255 ) ) background . paste ( t_image , mask = t_image . split ()[ 3 ] ) background . save ( 'qr.jpg' , \"JPEG\" , quality = 100 ) We use Image from python-pillow to change the background from transparent to white. Opencv seems unable to consume the qrcode when it has a transparent background. Extract Data From QRCODE class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): ... image = cv2 . imread ( 'qr.jpg' ) qr_det = cv2 . QRCodeDetector () qrdata = qr_det . detectAndDecode ( image ) totp_code = pyotp . TOTP ( pyotp . parse_uri ( qrdata [ 0 ]) . secret ) . now () qrdata[0] will be the otpauth_uri . pyotp.parse_uri(qrdata[0]).secret is the secret key. totp_code is the one-time password. POST the totp_code class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): ... totp_code = pyotp . TOTP ( pyotp . parse_uri ( qrdata [ 0 ]) . secret ) . now () response = self . client . post ( reverse ( 'accounts:enable_totp' ), { 'totp_code' : totp_code }, follow = True ) self . assertEquals ( response . status_code , 200 ) self . assertTemplateUsed ( response , 'base_form.html' ) self . assertEquals ( response . request [ 'PATH_INFO' ], '/accounts/edit-profile/' ) user_a = User . objects . get ( username = 'user_a' ) self . assertTrue ( user_a . account . use_totp ) self . assertEquals ( len ( user_a . account . totp_key ), 16 ) pathlib . Path ( 'qr.svg' ) . unlink () pathlib . Path ( 'qr.png' ) . unlink () pathlib . Path ( 'qr.jpg' ) . unlink () Post the totp_code back to the form, and then verify that the database is updated, and then delete the image files. Complete TestCase # tp/accounts/tests/test_enable_totp_view.py # python manage.py test accounts.tests.test_enable_totp_view from django.test import TestCase from django.contrib.auth.models import User from accounts.models import Account from django.urls import reverse from bs4 import BeautifulSoup import cv2 from cairosvg import svg2png from PIL import Image import pyotp class TestEnableTOTPViewTestCase ( TestCase ): # setUP TestCase def setUp ( self ): user_a = User . objects . create ( username = 'user_a' ) user_a . set_password ( 'password_user_a' ) user_a . save () Account . objects . create ( user = user_a ) def test_enable_totp_view ( self ): self . client . login ( username = 'user_a' , password = 'password_user_a' ) # GET Form get_response = self . client . get ( reverse ( 'accounts:enable_totp' ) ) self . assertEquals ( get_response . status_code , 200 ) self . assertTemplateUsed ( get_response , 'accounts/totp_form.html' ) self . assertEquals ( get_response . request [ 'PATH_INFO' ], '/accounts/enable-totp/' ) # Consume SVG with BeautifulSoup soup = BeautifulSoup ( get_response . content , features = \"lxml\" ) svg_container = soup . find ( \"div\" , { \"id\" : \"svgcontainer\" } ) self . assertIsNotNone ( svg_container ) scsvg = svg_container . findChild ( \"svg\" , recursive = False ) self . assertIsNotNone ( scsvg ) with open ( 'qr.svg' , 'w' ) as f : x_string = \"<?xml version='1.0'\" x_string += \" encoding='utf-8'?> \\n \" f . write ( x_string + str ( scsvg )) # svg2png svg2png ( url = 'qr.svg' , write_to = 'qr.png' , scale = 8 ) # add white background t_image = Image . open ( 'qr.png' ) t_image . load () background = Image . new ( \"RGB\" , t_image . size , ( 255 , 255 , 255 ) ) background . paste ( t_image , mask = t_image . split ()[ 3 ] ) background . save ( 'qr.jpg' , \"JPEG\" , quality = 100 ) # extract data from qrcode image = cv2 . imread ( 'qr.jpg' ) qr_det = cv2 . QRCodeDetector () qrdata = qr_det . detectAndDecode ( image ) totp_code = pyotp . TOTP ( pyotp . parse_uri ( qrdata [ 0 ]) . secret ) . now () totp_code = pyotp . TOTP ( pyotp . parse_uri ( qrdata [ 0 ]) . secret ) . now () # POST the `totp_code` response = self . client . post ( reverse ( 'accounts:enable_totp' ), { 'totp_code' : totp_code }, follow = True ) self . assertEquals ( response . status_code , 200 ) self . assertTemplateUsed ( response , 'base_form.html' ) self . assertEquals ( response . request [ 'PATH_INFO' ], '/accounts/edit-profile/' ) user_a = User . objects . get ( username = 'user_a' ) self . assertTrue ( user_a . account . use_totp ) self . assertEquals ( len ( user_a . account . totp_key ), 16 ) pathlib . Path ( 'qr.svg' ) . unlink () pathlib . Path ( 'qr.png' ) . unlink () pathlib . Path ( 'qr.jpg' ) . unlink ()","title":"Test QR SVG Django"},{"location":"posts/test-qr-svg-django/#introduction","text":"I worked out a solution in django-testing, for testing a view that renders a qrcode as an svg as an inline svg xml string. In case you are not familiar with svg , scalable vector graphics, it is an image that is completely rendered from a string of text, or more accurately XML text, unlike PNG or JPG , which are binary file s.","title":"Introduction"},{"location":"posts/test-qr-svg-django/#python-libraries-used","text":"BeautifulSoup to consume html CairoSVG to convert svg to png python-pillow to convert transparent png background to white opencv-python to extract data from qrcode python-pyotp","title":"Python Libraries Used"},{"location":"posts/test-qr-svg-django/#form-screenshot","text":"This is the form we will be testing. In real life you would confirm that you want to use two-factor-authentication by scanning the qrcode with an authentication app on your smartphone, and then enter the time-based one-time password . totp codes are derived from two things and two things only. A secret key and a time such as the current time. For instance, an authentication application can tell you what your totp code is, but of course in this testing scenario we use python-pyotp , after extracting the secret key from the qrcode. enable totp confirmation form","title":"Form ScreenShot"},{"location":"posts/test-qr-svg-django/#import-python-libraries","text":"# tp/accounts/tests/test_enable_totp_view.py # python manage.py test accounts.tests.test_enable_totp_view from django.test import TestCase from django.contrib.auth.models import User from accounts.models import Account from django.urls import reverse from bs4 import BeautifulSoup import cv2 from cairosvg import svg2png from PIL import Image import pyotp import pathlib The Account model has a one-to-one relationship with the Django built-in User model. This is where we keep track of the totp secret key and boolean value for having totp authentication enabled.","title":"Import Python Libraries"},{"location":"posts/test-qr-svg-django/#setup-testcase","text":"... import pathlib class TestEnableTOTPViewTestCase ( TestCase ): def setUp ( self ): user_a = User . objects . create ( username = 'user_a' ) user_a . set_password ( 'password_user_a' ) user_a . save () Account . objects . create ( user = user_a ) We create a test user and save that in the test database.","title":"setUp TestCase"},{"location":"posts/test-qr-svg-django/#get-form","text":"class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): self . client . login ( username = 'user_a' , password = 'password_user_a' ) get_response = self . client . get ( reverse ( 'accounts:enable_totp' ) ) self . assertEquals ( get_response . status_code , 200 ) self . assertTemplateUsed ( get_response , 'accounts/totp_form.html' ) self . assertEquals ( get_response . request [ 'PATH_INFO' ], '/accounts/enable-totp/' ) The TestCase requires two requests. In the first request we GET the form so that we can consume the qrcode.","title":"GET Form"},{"location":"posts/test-qr-svg-django/#consume-svg-with-beautifulsoup","text":"class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): ... soup = BeautifulSoup ( get_response . content , features = \"lxml\" ) svg_container = soup . find ( \"div\" , { \"id\" : \"svgcontainer\" } ) self . assertIsNotNone ( svg_container ) scsvg = svg_container . findChild ( \"svg\" , recursive = False ) self . assertIsNotNone ( scsvg ) with open ( 'qr.svg' , 'w' ) as f : x_string = \"<?xml version='1.0'\" x_string += \" encoding='utf-8'?> \\n \" f . write ( x_string + str ( scsvg )) The inline xml for the svg comes to us as a child of a div with an id of svgcontainer . We capture the xml of the svg in the variable scsvg , and then write it out to disc.","title":"Consume SVG with BeautifulSoup"},{"location":"posts/test-qr-svg-django/#svg2png","text":"class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): ... svg2png ( url = 'qr.svg' , write_to = 'qr.png' , scale = 8 ) With svg2png from CairoSVG, we convert the svg to png format. Opencv seems unable to consume the qrcode unless you scale it up.","title":"svg2png"},{"location":"posts/test-qr-svg-django/#add-white-background","text":"class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): ... t_image = Image . open ( 'qr.png' ) t_image . load () background = Image . new ( \"RGB\" , t_image . size , ( 255 , 255 , 255 ) ) background . paste ( t_image , mask = t_image . split ()[ 3 ] ) background . save ( 'qr.jpg' , \"JPEG\" , quality = 100 ) We use Image from python-pillow to change the background from transparent to white. Opencv seems unable to consume the qrcode when it has a transparent background.","title":"Add White Background"},{"location":"posts/test-qr-svg-django/#extract-data-from-qrcode","text":"class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): ... image = cv2 . imread ( 'qr.jpg' ) qr_det = cv2 . QRCodeDetector () qrdata = qr_det . detectAndDecode ( image ) totp_code = pyotp . TOTP ( pyotp . parse_uri ( qrdata [ 0 ]) . secret ) . now () qrdata[0] will be the otpauth_uri . pyotp.parse_uri(qrdata[0]).secret is the secret key. totp_code is the one-time password.","title":"Extract Data From QRCODE"},{"location":"posts/test-qr-svg-django/#post-the-totp_code","text":"class TestEnableTOTPViewTestCase ( TestCase ): ... def test_enable_totp_view ( self ): ... totp_code = pyotp . TOTP ( pyotp . parse_uri ( qrdata [ 0 ]) . secret ) . now () response = self . client . post ( reverse ( 'accounts:enable_totp' ), { 'totp_code' : totp_code }, follow = True ) self . assertEquals ( response . status_code , 200 ) self . assertTemplateUsed ( response , 'base_form.html' ) self . assertEquals ( response . request [ 'PATH_INFO' ], '/accounts/edit-profile/' ) user_a = User . objects . get ( username = 'user_a' ) self . assertTrue ( user_a . account . use_totp ) self . assertEquals ( len ( user_a . account . totp_key ), 16 ) pathlib . Path ( 'qr.svg' ) . unlink () pathlib . Path ( 'qr.png' ) . unlink () pathlib . Path ( 'qr.jpg' ) . unlink () Post the totp_code back to the form, and then verify that the database is updated, and then delete the image files.","title":"POST the totp_code"},{"location":"posts/test-qr-svg-django/#complete-testcase","text":"# tp/accounts/tests/test_enable_totp_view.py # python manage.py test accounts.tests.test_enable_totp_view from django.test import TestCase from django.contrib.auth.models import User from accounts.models import Account from django.urls import reverse from bs4 import BeautifulSoup import cv2 from cairosvg import svg2png from PIL import Image import pyotp class TestEnableTOTPViewTestCase ( TestCase ): # setUP TestCase def setUp ( self ): user_a = User . objects . create ( username = 'user_a' ) user_a . set_password ( 'password_user_a' ) user_a . save () Account . objects . create ( user = user_a ) def test_enable_totp_view ( self ): self . client . login ( username = 'user_a' , password = 'password_user_a' ) # GET Form get_response = self . client . get ( reverse ( 'accounts:enable_totp' ) ) self . assertEquals ( get_response . status_code , 200 ) self . assertTemplateUsed ( get_response , 'accounts/totp_form.html' ) self . assertEquals ( get_response . request [ 'PATH_INFO' ], '/accounts/enable-totp/' ) # Consume SVG with BeautifulSoup soup = BeautifulSoup ( get_response . content , features = \"lxml\" ) svg_container = soup . find ( \"div\" , { \"id\" : \"svgcontainer\" } ) self . assertIsNotNone ( svg_container ) scsvg = svg_container . findChild ( \"svg\" , recursive = False ) self . assertIsNotNone ( scsvg ) with open ( 'qr.svg' , 'w' ) as f : x_string = \"<?xml version='1.0'\" x_string += \" encoding='utf-8'?> \\n \" f . write ( x_string + str ( scsvg )) # svg2png svg2png ( url = 'qr.svg' , write_to = 'qr.png' , scale = 8 ) # add white background t_image = Image . open ( 'qr.png' ) t_image . load () background = Image . new ( \"RGB\" , t_image . size , ( 255 , 255 , 255 ) ) background . paste ( t_image , mask = t_image . split ()[ 3 ] ) background . save ( 'qr.jpg' , \"JPEG\" , quality = 100 ) # extract data from qrcode image = cv2 . imread ( 'qr.jpg' ) qr_det = cv2 . QRCodeDetector () qrdata = qr_det . detectAndDecode ( image ) totp_code = pyotp . TOTP ( pyotp . parse_uri ( qrdata [ 0 ]) . secret ) . now () totp_code = pyotp . TOTP ( pyotp . parse_uri ( qrdata [ 0 ]) . secret ) . now () # POST the `totp_code` response = self . client . post ( reverse ( 'accounts:enable_totp' ), { 'totp_code' : totp_code }, follow = True ) self . assertEquals ( response . status_code , 200 ) self . assertTemplateUsed ( response , 'base_form.html' ) self . assertEquals ( response . request [ 'PATH_INFO' ], '/accounts/edit-profile/' ) user_a = User . objects . get ( username = 'user_a' ) self . assertTrue ( user_a . account . use_totp ) self . assertEquals ( len ( user_a . account . totp_key ), 16 ) pathlib . Path ( 'qr.svg' ) . unlink () pathlib . Path ( 'qr.png' ) . unlink () pathlib . Path ( 'qr.jpg' ) . unlink ()","title":"Complete TestCase"},{"location":"posts/trents-favorite-podcasts/","text":"date: 2021-07-23 Introduction Someone asked me 10 years ago what are my favorite podcasts, so here you go. Note that a lot of podcasts tend to improve over time as the hosts become more skilled at production. Now you see why road trips with me are so much fun! To subscribe to any podcast, simply paste the rss link in to any podcast client application. My preference on Android is AntennaPod , but there are many others to choose from. Categories RSS Feeds , Alphabetical , Android , Chaos , Czechia , Dart , Development , Django , Entertaining , Engineering , Europe , Feudalism , Germany , Greece , History , HomeLab , IOT , Java , Javascript , Kotlin , Linux , linuxMint , MiddleAges , OpenSource , Politics , Python , React , Reformation , Slackware , SBCs , SysAdmin , Travel , Tutorial , Ubuntu , Web Alphabetical 2.5 Admins Host Joe Ressington is a podcast production savant. Jim Salter is a SysAdmin and tech journalist and a developer of ZFS utilities. Allan Jude is a FreeBSD developer who operates a video streaming service called Scale Engine . 2.5 Admins RSS Ask Noah Show Ask Noah Show is a Radio Callin Show about Linux and Open Source. Host Noah Chelliah operates AltiSpeed , an IT company in Fargo. Ask Noah Show RSS Bohemican Podcast Bohemican Podcast is a podcast about the history of Bohemia and Moravia including travel tips. Hosts Travis Dow and Pete Coleman are Americans living and working in Prague. Bohemican RSS Coder Radio Coder Radio features Mike Dominick . Coder Radio RSS Destination Linux Destination Linux is a round-table podcast about Linux. Destination Linux RSS Django Chat Django Chat is a two-man team podcast about the development and community of Django , which is an opinionated and structured python web-application framework. Django Chat RSS Django Riffs Django Riffs is an instructional, tutorial podcast about Django , which is an opinionated and structured python web-application framework. Django Riffs RSS Engines Of Our Ingenuity Engines Of Our Ingenuity is a short daily podcast about engineering and culture. Engines Of Our Ingenuity FLOSS Weekly FLOSS Weekly is a podcast about free, libre, open-source software. FLOSS Weekly RSS GNU World Order GNU World Order is a podcast about Linux, especially Slackware. GNU World Order RSS HardCore History HardCore History is an exciting and entertaining history podcast, mostly focusing on war. HardCore History RSS History Of Germany Unfortunately, the History Of Germany podcast, hosted by Travis Dow, suffers from sputtery repitition, um you know sputtery repitition, circular explanations, so like circular explanations, and some minor valley talk and vocal fry issues, so um like yeah. But the subject target is rich, exhaustive, and comprehensive, and covers the history of both Germany and Germanic peoples and tribes beginning with the Neanderthals. History Of Germany RSS In Our Time: History In Our Time: History is a lively and argumentative history conversation betwixt a revolving round-table of academic historians. In Our Time: History RSS It's All Widgets Flutter Podcast It's All Widgets Flutter Podcast is a podcast about Flutter . It's All Widgets Flutter Podcast RSS Late Night Linux Late Night Linux is a round-table podcast of exquisite production value, about Linux. Late Night Linux RSS Linux Action News Linux Action News is a weekly podcast of Linux News. Linux Action News RSS Linux InLaws Linux InLaws ist ein bier-sodden Podcast \u00fcber Linux. Linux InLaws RSS Linux Unplugged Linux Unplugged is a podcast about Linux with live audience participation via mumble. Linux Unplugged RSS MiniPC MiniPC is a round-table podcast about SBC computers, such as the RaspberryPI, as well as IOT. MiniPC RSS MintCast MintCast is a long-running round-table podcast by the Linux Mint community for all users of Linux. MintCast RSS No Agenda No Agenda Host Adam Curry is a former MTV Personality from the 80s, and consequently the podcast has a very high production value. Host John C Dvorak is a tech journalist. No Agenda RSS Open Source Voices Open Source Voices is an interview podcast featuring open-source personalities, hosted by J.T. Pennington Open Source Voices Python Bytes Python Bytes is a podcast about Python. Python Bytes RSS React Podcast React Podcast is a podcast about javascript web development with react . React Podcast RSS Scholars & Sense Host Victor Davis Hanson is a Classicist and Historian. Scholars & Sense RSS Self-Hosted Self-Hosted is a podcast about self-hosting, homelabs, and IOT. Self-Hosted RSS Sunday Morning Linux Review Sunday Morning Linux Review is an occassional round-table podcast about Linux. Sunday Morning Linux Review RSS Syntax Syntax is a podcast about web development. Syntax RSS Talk Python To Me Talk Python To Me is a podcast about python. Talk Python To Me RSS Talking Kotlin Talking Kotlin is a podcast about the Kotlin Programming Language , which is the default programming language for Android Applications. Both kotlin and the podcast are created by JetBrains , which is a company that creates and sells professional IDE's, from which Android Studio is derived. Talking Kotlin RSS The History Of Ancient Greece Unfortunately, The History Of Ancient Greece lacks expressiveness and poetic phrasing. Your Bible-reading Grandpa's lyrical, King's-English word-smithing this is not. However, the abundance of content is thorough and exhaustive. The History Of Ancient Greece RSS The Matt Freire Show The Matt Freire Show is an occassional interview podcast about development. The Matt Freire Show RSS The Mike Dominick Show The Mike Dominick Show is an occassional interview podcast about development. The Mike Dominick Show RSS Ubuntu Podcast Ubuntu Podcast is a podcast about Linux and Ubuntu , hosted by a round-table of current and former Ubuntu users and employees. Ubuntu Podcast RSS Wittenberg To Westphalia Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS You're Dead To Me You're Dead To Me , is a fun and serious history and humour podcast featuring a different academic historian, and a different professional comic personality each episode. You're Dead To Me RSS Categories Android It's All Widgets Flutter Podcast It's All Widgets Flutter Podcast is a podcast about Flutter . It's All Widgets Flutter Podcast RSS Talking Kotlin Talking Kotlin is a podcast about the Kotlin Programming Language , which is the default programming language for Android Applications. Both kotlin and the podcast are created by JetBrains , which is a company that creates and sells professional IDE's, from which Android Studio is derived. Talking Kotlin RSS Categories Chaos Wittenberg To Westphalia Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories Czechia Bohemican Podcast Bohemican Podcast is a podcast about the history of Bohemia and Moravia including travel tips. Hosts Travis Dow and Pete Coleman are Americans living and working in Prague. Bohemican RSS Categories Dart It's All Widgets Flutter Podcast It's All Widgets Flutter Podcast is a podcast about Flutter . It's All Widgets Flutter Podcast RSS Categories Development Coder Radio Coder Radio features Mike Dominick . Coder Radio RSS Django Chat Django Chat is a two-man team podcast about the development and community of Django , which is an opinionated and structured python web-application framework. Django Chat RSS Django Riffs Django Riffs is an instructional, tutorial podcast about Django , which is an opinionated and structured python web-application framework. Django Riffs RSS It's All Widgets Flutter Podcast It's All Widgets Flutter Podcast is a podcast about Flutter . It's All Widgets Flutter Podcast RSS Python Bytes Python Bytes is a podcast about Python. Python Bytes RSS React Podcast React Podcast is a podcast about javascript web development with react . React Podcast RSS Syntax Syntax is a podcast about web development. Syntax RSS Talk Python To Me Talk Python To Me is a podcast about python. Talk Python To Me RSS Talking Kotlin Talking Kotlin is a podcast about the Kotlin Programming Language , which is the default programming language for Android Applications. Both kotlin and the podcast are created by JetBrains , which is a company that creates and sells professional IDE's, from which Android Studio is derived. Talking Kotlin RSS The Matt Freire Show The Matt Freire Show is an occassional interview podcast about development. The Matt Freire Show RSS The Mike Dominick Show The Mike Dominick Show is an occassional interview podcast about development. The Mike Dominick Show RSS Categories Django Django Chat Django Chat is a two-man team podcast about the development and community of Django , which is an opinionated and structured python web-application framework. Django Chat RSS Django Riffs Django Riffs is an instructional, tutorial podcast about Django , which is an opinionated and structured python web-application framework. Django Riffs RSS Categories Entertaining HardCore History HardCore History is an exciting and entertaining history podcast, mostly focusing on war. HardCore History RSS No Agenda No Agenda Host Adam Curry is a former MTV Personality from the 80s, and consequently the podcast has a very high production value. Host John C Dvorak is a tech journalist. No Agenda RSS You're Dead To Me You're Dead To Me , is a fun and serious history and humour podcast featuring a different academic historian, and a different professional comic personality each episode. You're Dead To Me RSS Categories Engineering Engines Of Our Ingenuity Engines Of Our Ingenuity is a short daily podcast about engineering and culture. Engines Of Our Ingenuity Categories Europe Wittenberg To Westphalia Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories Feudalism Wittenberg To Westphalia Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories Germany History Of Germany Unfortunately, the History Of Germany podcast, hosted by Travis Dow, suffers from sputtery repitition, um you know sputtery repitition, circular explanations, so like circular explanations, and some minor valley talk and vocal fry issues, so um like yeah. But the subject target is rich, exhaustive, and comprehensive, and covers the history of both Germany and Germanic peoples and tribes beginning with the Neanderthals. History Of Germany RSS Wittenberg To Westphalia Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories Greece The History Of Ancient Greece Unfortunately, The History Of Ancient Greece lacks expressiveness and poetic phrasing. Your Bible-reading Grandpa's lyrical, King's-English word-smithing this is not. However, the abundance of content is thorough and exhaustive. The History Of Ancient Greece RSS Categories History Bohemican Podcast Bohemican Podcast is a podcast about the history of Bohemia and Moravia including travel tips. Hosts Travis Dow and Pete Coleman are Americans living and working in Prague. Bohemican RSS HardCore History HardCore History is an exciting and entertaining history podcast, mostly focusing on war. HardCore History RSS History Of Germany Unfortunately, the History Of Germany podcast, hosted by Travis Dow, suffers from sputtery repitition, um you know sputtery repitition, circular explanations, so like circular explanations, and some minor valley talk and vocal fry issues, so um like yeah. But the subject target is rich, exhaustive, and comprehensive, and covers the history of both Germany and Germanic peoples and tribes beginning with the Neanderthals. History Of Germany RSS In Our Time: History In Our Time: History is a lively and argumentative history conversation betwixt a revolving round-table of academic historians. In Our Time: History RSS The History Of Ancient Greece Unfortunately, The History Of Ancient Greece lacks expressiveness and poetic phrasing. Your Bible-reading Grandpa's lyrical, King's-English word-smithing this is not. However, the abundance of content is thorough and exhaustive. The History Of Ancient Greece RSS Wittenberg To Westphalia Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS You're Dead To Me You're Dead To Me , is a fun and serious history and humour podcast featuring a different academic historian, and a different professional comic personality each episode. You're Dead To Me RSS Categories Home Lab Self-Hosted Self-Hosted is a podcast about self-hosting, homelabs, and IOT. Self-Hosted RSS Categories Internet of Things MiniPC MiniPC is a round-table podcast about SBC computers, such as the RaspberryPI, as well as IOT. MiniPC RSS Self-Hosted Self-Hosted is a podcast about self-hosting, homelabs, and IOT. Self-Hosted RSS Self-Hosted Self-Hosted is a podcast about self-hosting, homelabs, and IOT. Self-Hosted RSS Categories Java Talking Kotlin Talking Kotlin is a podcast about the Kotlin Programming Language , which is the default programming language for Android Applications. Both kotlin and the podcast are created by JetBrains , which is a company that creates and sells professional IDE's, from which Android Studio is derived. Talking Kotlin RSS Categories Javascript React Podcast React Podcast is a podcast about javascript web development with react . React Podcast RSS Syntax Syntax is a podcast about web development. Syntax RSS Categories Kotlin Talking Kotlin Talking Kotlin is a podcast about the Kotlin Programming Language , which is the default programming language for Android Applications. Both kotlin and the podcast are created by JetBrains , which is a company that creates and sells professional IDE's, from which Android Studio is derived. Talking Kotlin RSS Categories Linux Ask Noah Show Ask Noah Show is a Radio Callin Show about Linux and Open Source. Host Noah Chelliah operates AltiSpeed , an IT company in Fargo. Ask Noah Show RSS Destination Linux Destination Linux is a round-table podcast about Linux. Destination Linux RSS GNU World Order GNU World Order is a podcast about Linux, especially Slackware. GNU World Order RSS Late Night Linux Late Night Linux is a round-table podcast of exquisite production value, about Linux. Late Night Linux RSS Linux Action News Linux Action News is a weekly podcast of Linux News. Linux Action News RSS Linux InLaws Linux InLaws ist ein bier-sodden Podcast \u00fcber Linux. Linux InLaws RSS Linux Unplugged Linux Unplugged is a podcast about Linux with live audience participation via mumble. Linux Unplugged RSS MiniPC MiniPC is a round-table podcast about SBC computers, such as the RaspberryPI, as well as IOT. MiniPC RSS MintCast MintCast is a long-running round-table podcast by the Linux Mint community for all users of Linux. MintCast RSS Self-Hosted Self-Hosted is a podcast about self-hosting, homelabs, and IOT. Self-Hosted RSS Sunday Morning Linux Review Sunday Morning Linux Review is an occassional round-table podcast about Linux. Sunday Morning Linux Review RSS Ubuntu Podcast Ubuntu Podcast is a podcast about Linux and Ubuntu , hosted by a round-table of current and former Ubuntu users and employees. Ubuntu Podcast RSS Categories Linux Mint MintCast MintCast is a long-running round-table podcast by the Linux Mint community for all users of Linux. MintCast RSS Categories Middle Ages Wittenberg To Westphalia Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories Open Source Ask Noah Show Ask Noah Show is a Radio Callin Show about Linux and Open Source. Host Noah Chelliah operates AltiSpeed , an IT company in Fargo. Ask Noah Show RSS FLOSS Weekly FLOSS Weekly is a podcast about free, libre, open-source software. FLOSS Weekly RSS MiniPC MiniPC is a round-table podcast about SBC computers, such as the RaspberryPI, as well as IOT. MiniPC RSS Open Source Voices Open Source Voices is an interview podcast featuring open-source personalities, hosted by J.T. Pennington Open Source Voices Categories Politics No Agenda No Agenda Host Adam Curry is a former MTV Personality from the 80s, and consequently the podcast has a very high production value. Host John C Dvorak is a tech journalist. No Agenda RSS Scholars & Sense Host Victor Davis Hanson is a Classicist and Historian. Scholars & Sense RSS Categories Python Django Chat Django Chat is a two-man team podcast about the development and community of Django , which is an opinionated and structured python web-application framework. Django Chat RSS Django Riffs Django Riffs is an instructional, tutorial podcast about Django , which is an opinionated and structured python web-application framework. Django Riffs RSS Python Bytes Python Bytes is a podcast about Python. Python Bytes RSS Talk Python To Me Talk Python To Me is a podcast about python. Talk Python To Me RSS Categories React React Podcast React Podcast is a podcast about javascript web development with react . React Podcast RSS Categories Reformation Wittenberg To Westphalia Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories Slackware GNU World Order GNU World Order is a podcast about Linux, especially Slackware. GNU World Order RSS Categories Small Board Computers MiniPC MiniPC is a round-table podcast about SBC computers, such as the RaspberryPI, as well as IOT. MiniPC RSS Categories SysAdmin 2.5 Admins Host Joe Ressington is a podcast production savant. Jim Salter is a SysAdmin and tech journalist and a developer of ZFS utilities. Allan Jude is a FreeBSD developer who operates a video streaming service called Scale Engine . 2.5 Admins RSS Ask Noah Show Ask Noah Show is a Radio Callin Show about Linux and Open Source. Host Noah Chelliah operates AltiSpeed , an IT company in Fargo. Ask Noah Show RSS Categories Travel Bohemican Podcast Bohemican Podcast is a podcast about the history of Bohemia and Moravia including travel tips. Hosts Travis Dow and Pete Coleman are Americans living and working in Prague. Bohemican RSS Categories Tutorial Django Riffs Django Riffs is an instructional, tutorial podcast about Django , which is an opinionated and structured python web-application framework. Django Riffs RSS Categories Ubuntu Ubuntu Podcast Ubuntu Podcast is a podcast about Linux and Ubuntu , hosted by a round-table of current and former Ubuntu users and employees. Ubuntu Podcast RSS Categories Web Django Chat Django Chat is a two-man team podcast about the development and community of Django , which is an opinionated and structured python web-application framework. Django Chat RSS It's All Widgets Flutter Podcast It's All Widgets Flutter Podcast is a podcast about Flutter . It's All Widgets Flutter Podcast RSS React Podcast React Podcast is a podcast about javascript web development with react . React Podcast RSS Syntax Syntax is a podcast about web development. Syntax RSS Categories RSS Feeds 2.5 Admins RSS Ask Noah Show RSS Bohemican RSS Coder Radio RSS Destination Linux RSS Django Chat RSS Django Riffs RSS Engines Of Our Ingenuity FLOSS Weekly RSS GNU World Order RSS HardCore History RSS History Of Germany RSS In Our Time: History RSS It's All Widgets Flutter Podcast RSS Late Night Linux RSS Linux Action News RSS Linux InLaws RSS Linux Unplugged RSS MiniPC RSS MintCast RSS No Agenda RSS Open Source Voices Python Bytes RSS React Podcast RSS Scholars & Sense RSS Self-Hosted RSS Sunday Morning Linux Review RSS Syntax RSS Talk Python To Me RSS Talking Kotlin RSS The History Of Ancient Greece RSS The Matt Freire Show RSS The Mike Dominick Show RSS Ubuntu Podcast RSS Wittenberg To Westphalia RSS You're Dead To Me RSS Categories","title":"Trent's Favorite Podcasts"},{"location":"posts/trents-favorite-podcasts/#introduction","text":"Someone asked me 10 years ago what are my favorite podcasts, so here you go. Note that a lot of podcasts tend to improve over time as the hosts become more skilled at production. Now you see why road trips with me are so much fun! To subscribe to any podcast, simply paste the rss link in to any podcast client application. My preference on Android is AntennaPod , but there are many others to choose from.","title":"Introduction"},{"location":"posts/trents-favorite-podcasts/#categories","text":"RSS Feeds , Alphabetical , Android , Chaos , Czechia , Dart , Development , Django , Entertaining , Engineering , Europe , Feudalism , Germany , Greece , History , HomeLab , IOT , Java , Javascript , Kotlin , Linux , linuxMint , MiddleAges , OpenSource , Politics , Python , React , Reformation , Slackware , SBCs , SysAdmin , Travel , Tutorial , Ubuntu , Web","title":"Categories"},{"location":"posts/trents-favorite-podcasts/#alphabetical","text":"","title":"Alphabetical"},{"location":"posts/trents-favorite-podcasts/#25-admins","text":"Host Joe Ressington is a podcast production savant. Jim Salter is a SysAdmin and tech journalist and a developer of ZFS utilities. Allan Jude is a FreeBSD developer who operates a video streaming service called Scale Engine . 2.5 Admins RSS","title":"2.5 Admins"},{"location":"posts/trents-favorite-podcasts/#ask-noah-show","text":"Ask Noah Show is a Radio Callin Show about Linux and Open Source. Host Noah Chelliah operates AltiSpeed , an IT company in Fargo. Ask Noah Show RSS","title":"Ask Noah Show"},{"location":"posts/trents-favorite-podcasts/#bohemican-podcast","text":"Bohemican Podcast is a podcast about the history of Bohemia and Moravia including travel tips. Hosts Travis Dow and Pete Coleman are Americans living and working in Prague. Bohemican RSS","title":"Bohemican Podcast"},{"location":"posts/trents-favorite-podcasts/#coder-radio","text":"Coder Radio features Mike Dominick . Coder Radio RSS","title":"Coder Radio"},{"location":"posts/trents-favorite-podcasts/#destination-linux","text":"Destination Linux is a round-table podcast about Linux. Destination Linux RSS","title":"Destination Linux"},{"location":"posts/trents-favorite-podcasts/#django-chat","text":"Django Chat is a two-man team podcast about the development and community of Django , which is an opinionated and structured python web-application framework. Django Chat RSS","title":"Django Chat"},{"location":"posts/trents-favorite-podcasts/#django-riffs","text":"Django Riffs is an instructional, tutorial podcast about Django , which is an opinionated and structured python web-application framework. Django Riffs RSS","title":"Django Riffs"},{"location":"posts/trents-favorite-podcasts/#engines-of-our-ingenuity","text":"Engines Of Our Ingenuity is a short daily podcast about engineering and culture. Engines Of Our Ingenuity","title":"Engines Of Our Ingenuity"},{"location":"posts/trents-favorite-podcasts/#floss-weekly","text":"FLOSS Weekly is a podcast about free, libre, open-source software. FLOSS Weekly RSS","title":"FLOSS Weekly"},{"location":"posts/trents-favorite-podcasts/#gnu-world-order","text":"GNU World Order is a podcast about Linux, especially Slackware. GNU World Order RSS","title":"GNU World Order"},{"location":"posts/trents-favorite-podcasts/#hardcore-history","text":"HardCore History is an exciting and entertaining history podcast, mostly focusing on war. HardCore History RSS","title":"HardCore History"},{"location":"posts/trents-favorite-podcasts/#history-of-germany","text":"Unfortunately, the History Of Germany podcast, hosted by Travis Dow, suffers from sputtery repitition, um you know sputtery repitition, circular explanations, so like circular explanations, and some minor valley talk and vocal fry issues, so um like yeah. But the subject target is rich, exhaustive, and comprehensive, and covers the history of both Germany and Germanic peoples and tribes beginning with the Neanderthals. History Of Germany RSS","title":"History Of Germany"},{"location":"posts/trents-favorite-podcasts/#in-our-time-history","text":"In Our Time: History is a lively and argumentative history conversation betwixt a revolving round-table of academic historians. In Our Time: History RSS","title":"In Our Time: History"},{"location":"posts/trents-favorite-podcasts/#its-all-widgets-flutter-podcast","text":"It's All Widgets Flutter Podcast is a podcast about Flutter . It's All Widgets Flutter Podcast RSS","title":"It's All Widgets Flutter Podcast"},{"location":"posts/trents-favorite-podcasts/#late-night-linux","text":"Late Night Linux is a round-table podcast of exquisite production value, about Linux. Late Night Linux RSS","title":"Late Night Linux"},{"location":"posts/trents-favorite-podcasts/#linux-action-news","text":"Linux Action News is a weekly podcast of Linux News. Linux Action News RSS","title":"Linux Action News"},{"location":"posts/trents-favorite-podcasts/#linux-inlaws","text":"Linux InLaws ist ein bier-sodden Podcast \u00fcber Linux. Linux InLaws RSS","title":"Linux InLaws"},{"location":"posts/trents-favorite-podcasts/#linux-unplugged","text":"Linux Unplugged is a podcast about Linux with live audience participation via mumble. Linux Unplugged RSS","title":"Linux Unplugged"},{"location":"posts/trents-favorite-podcasts/#minipc","text":"MiniPC is a round-table podcast about SBC computers, such as the RaspberryPI, as well as IOT. MiniPC RSS","title":"MiniPC"},{"location":"posts/trents-favorite-podcasts/#mintcast","text":"MintCast is a long-running round-table podcast by the Linux Mint community for all users of Linux. MintCast RSS","title":"MintCast"},{"location":"posts/trents-favorite-podcasts/#no-agenda","text":"No Agenda Host Adam Curry is a former MTV Personality from the 80s, and consequently the podcast has a very high production value. Host John C Dvorak is a tech journalist. No Agenda RSS","title":"No Agenda"},{"location":"posts/trents-favorite-podcasts/#open-source-voices","text":"Open Source Voices is an interview podcast featuring open-source personalities, hosted by J.T. Pennington Open Source Voices","title":"Open Source Voices"},{"location":"posts/trents-favorite-podcasts/#python-bytes","text":"Python Bytes is a podcast about Python. Python Bytes RSS","title":"Python Bytes"},{"location":"posts/trents-favorite-podcasts/#react-podcast","text":"React Podcast is a podcast about javascript web development with react . React Podcast RSS","title":"React Podcast"},{"location":"posts/trents-favorite-podcasts/#scholars-sense","text":"Host Victor Davis Hanson is a Classicist and Historian. Scholars & Sense RSS","title":"Scholars &amp; Sense"},{"location":"posts/trents-favorite-podcasts/#self-hosted","text":"Self-Hosted is a podcast about self-hosting, homelabs, and IOT. Self-Hosted RSS","title":"Self-Hosted"},{"location":"posts/trents-favorite-podcasts/#sunday-morning-linux-review","text":"Sunday Morning Linux Review is an occassional round-table podcast about Linux. Sunday Morning Linux Review RSS","title":"Sunday Morning Linux Review"},{"location":"posts/trents-favorite-podcasts/#syntax","text":"Syntax is a podcast about web development. Syntax RSS","title":"Syntax"},{"location":"posts/trents-favorite-podcasts/#talk-python-to-me","text":"Talk Python To Me is a podcast about python. Talk Python To Me RSS","title":"Talk Python To Me"},{"location":"posts/trents-favorite-podcasts/#talking-kotlin","text":"Talking Kotlin is a podcast about the Kotlin Programming Language , which is the default programming language for Android Applications. Both kotlin and the podcast are created by JetBrains , which is a company that creates and sells professional IDE's, from which Android Studio is derived. Talking Kotlin RSS","title":"Talking Kotlin"},{"location":"posts/trents-favorite-podcasts/#the-history-of-ancient-greece","text":"Unfortunately, The History Of Ancient Greece lacks expressiveness and poetic phrasing. Your Bible-reading Grandpa's lyrical, King's-English word-smithing this is not. However, the abundance of content is thorough and exhaustive. The History Of Ancient Greece RSS","title":"The History Of Ancient Greece"},{"location":"posts/trents-favorite-podcasts/#the-matt-freire-show","text":"The Matt Freire Show is an occassional interview podcast about development. The Matt Freire Show RSS","title":"The Matt Freire Show"},{"location":"posts/trents-favorite-podcasts/#the-mike-dominick-show","text":"The Mike Dominick Show is an occassional interview podcast about development. The Mike Dominick Show RSS","title":"The Mike Dominick Show"},{"location":"posts/trents-favorite-podcasts/#ubuntu-podcast","text":"Ubuntu Podcast is a podcast about Linux and Ubuntu , hosted by a round-table of current and former Ubuntu users and employees. Ubuntu Podcast RSS","title":"Ubuntu Podcast"},{"location":"posts/trents-favorite-podcasts/#wittenberg-to-westphalia","text":"Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS","title":"Wittenberg To Westphalia"},{"location":"posts/trents-favorite-podcasts/#youre-dead-to-me","text":"You're Dead To Me , is a fun and serious history and humour podcast featuring a different academic historian, and a different professional comic personality each episode. You're Dead To Me RSS Categories","title":"You're Dead To Me"},{"location":"posts/trents-favorite-podcasts/#android","text":"","title":"Android"},{"location":"posts/trents-favorite-podcasts/#its-all-widgets-flutter-podcast_1","text":"It's All Widgets Flutter Podcast is a podcast about Flutter . It's All Widgets Flutter Podcast RSS","title":"It's All Widgets Flutter Podcast"},{"location":"posts/trents-favorite-podcasts/#talking-kotlin_1","text":"Talking Kotlin is a podcast about the Kotlin Programming Language , which is the default programming language for Android Applications. Both kotlin and the podcast are created by JetBrains , which is a company that creates and sells professional IDE's, from which Android Studio is derived. Talking Kotlin RSS Categories","title":"Talking Kotlin"},{"location":"posts/trents-favorite-podcasts/#chaos","text":"","title":"Chaos"},{"location":"posts/trents-favorite-podcasts/#wittenberg-to-westphalia_1","text":"Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories","title":"Wittenberg To Westphalia"},{"location":"posts/trents-favorite-podcasts/#czechia","text":"","title":"Czechia"},{"location":"posts/trents-favorite-podcasts/#bohemican-podcast_1","text":"Bohemican Podcast is a podcast about the history of Bohemia and Moravia including travel tips. Hosts Travis Dow and Pete Coleman are Americans living and working in Prague. Bohemican RSS Categories","title":"Bohemican Podcast"},{"location":"posts/trents-favorite-podcasts/#dart","text":"","title":"Dart"},{"location":"posts/trents-favorite-podcasts/#its-all-widgets-flutter-podcast_2","text":"It's All Widgets Flutter Podcast is a podcast about Flutter . It's All Widgets Flutter Podcast RSS Categories","title":"It's All Widgets Flutter Podcast"},{"location":"posts/trents-favorite-podcasts/#development","text":"","title":"Development"},{"location":"posts/trents-favorite-podcasts/#coder-radio_1","text":"Coder Radio features Mike Dominick . Coder Radio RSS","title":"Coder Radio"},{"location":"posts/trents-favorite-podcasts/#django-chat_1","text":"Django Chat is a two-man team podcast about the development and community of Django , which is an opinionated and structured python web-application framework. Django Chat RSS","title":"Django Chat"},{"location":"posts/trents-favorite-podcasts/#django-riffs_1","text":"Django Riffs is an instructional, tutorial podcast about Django , which is an opinionated and structured python web-application framework. Django Riffs RSS","title":"Django Riffs"},{"location":"posts/trents-favorite-podcasts/#its-all-widgets-flutter-podcast_3","text":"It's All Widgets Flutter Podcast is a podcast about Flutter . It's All Widgets Flutter Podcast RSS","title":"It's All Widgets Flutter Podcast"},{"location":"posts/trents-favorite-podcasts/#python-bytes_1","text":"Python Bytes is a podcast about Python. Python Bytes RSS","title":"Python Bytes"},{"location":"posts/trents-favorite-podcasts/#react-podcast_1","text":"React Podcast is a podcast about javascript web development with react . React Podcast RSS","title":"React Podcast"},{"location":"posts/trents-favorite-podcasts/#syntax_1","text":"Syntax is a podcast about web development. Syntax RSS","title":"Syntax"},{"location":"posts/trents-favorite-podcasts/#talk-python-to-me_1","text":"Talk Python To Me is a podcast about python. Talk Python To Me RSS","title":"Talk Python To Me"},{"location":"posts/trents-favorite-podcasts/#talking-kotlin_2","text":"Talking Kotlin is a podcast about the Kotlin Programming Language , which is the default programming language for Android Applications. Both kotlin and the podcast are created by JetBrains , which is a company that creates and sells professional IDE's, from which Android Studio is derived. Talking Kotlin RSS","title":"Talking Kotlin"},{"location":"posts/trents-favorite-podcasts/#the-matt-freire-show_1","text":"The Matt Freire Show is an occassional interview podcast about development. The Matt Freire Show RSS","title":"The Matt Freire Show"},{"location":"posts/trents-favorite-podcasts/#the-mike-dominick-show_1","text":"The Mike Dominick Show is an occassional interview podcast about development. The Mike Dominick Show RSS Categories","title":"The Mike Dominick Show"},{"location":"posts/trents-favorite-podcasts/#django","text":"","title":"Django"},{"location":"posts/trents-favorite-podcasts/#django-chat_2","text":"Django Chat is a two-man team podcast about the development and community of Django , which is an opinionated and structured python web-application framework. Django Chat RSS","title":"Django Chat"},{"location":"posts/trents-favorite-podcasts/#django-riffs_2","text":"Django Riffs is an instructional, tutorial podcast about Django , which is an opinionated and structured python web-application framework. Django Riffs RSS Categories","title":"Django Riffs"},{"location":"posts/trents-favorite-podcasts/#entertaining","text":"","title":"Entertaining"},{"location":"posts/trents-favorite-podcasts/#hardcore-history_1","text":"HardCore History is an exciting and entertaining history podcast, mostly focusing on war. HardCore History RSS","title":"HardCore History"},{"location":"posts/trents-favorite-podcasts/#no-agenda_1","text":"No Agenda Host Adam Curry is a former MTV Personality from the 80s, and consequently the podcast has a very high production value. Host John C Dvorak is a tech journalist. No Agenda RSS","title":"No Agenda"},{"location":"posts/trents-favorite-podcasts/#youre-dead-to-me_1","text":"You're Dead To Me , is a fun and serious history and humour podcast featuring a different academic historian, and a different professional comic personality each episode. You're Dead To Me RSS Categories","title":"You're Dead To Me"},{"location":"posts/trents-favorite-podcasts/#engineering","text":"","title":"Engineering"},{"location":"posts/trents-favorite-podcasts/#engines-of-our-ingenuity_1","text":"Engines Of Our Ingenuity is a short daily podcast about engineering and culture. Engines Of Our Ingenuity Categories","title":"Engines Of Our Ingenuity"},{"location":"posts/trents-favorite-podcasts/#europe","text":"","title":"Europe"},{"location":"posts/trents-favorite-podcasts/#wittenberg-to-westphalia_2","text":"Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories","title":"Wittenberg To Westphalia"},{"location":"posts/trents-favorite-podcasts/#feudalism","text":"","title":"Feudalism"},{"location":"posts/trents-favorite-podcasts/#wittenberg-to-westphalia_3","text":"Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories","title":"Wittenberg To Westphalia"},{"location":"posts/trents-favorite-podcasts/#germany","text":"","title":"Germany"},{"location":"posts/trents-favorite-podcasts/#history-of-germany_1","text":"Unfortunately, the History Of Germany podcast, hosted by Travis Dow, suffers from sputtery repitition, um you know sputtery repitition, circular explanations, so like circular explanations, and some minor valley talk and vocal fry issues, so um like yeah. But the subject target is rich, exhaustive, and comprehensive, and covers the history of both Germany and Germanic peoples and tribes beginning with the Neanderthals. History Of Germany RSS","title":"History Of Germany"},{"location":"posts/trents-favorite-podcasts/#wittenberg-to-westphalia_4","text":"Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories","title":"Wittenberg To Westphalia"},{"location":"posts/trents-favorite-podcasts/#greece","text":"","title":"Greece"},{"location":"posts/trents-favorite-podcasts/#the-history-of-ancient-greece_1","text":"Unfortunately, The History Of Ancient Greece lacks expressiveness and poetic phrasing. Your Bible-reading Grandpa's lyrical, King's-English word-smithing this is not. However, the abundance of content is thorough and exhaustive. The History Of Ancient Greece RSS Categories","title":"The History Of Ancient Greece"},{"location":"posts/trents-favorite-podcasts/#history","text":"","title":"History"},{"location":"posts/trents-favorite-podcasts/#bohemican-podcast_2","text":"Bohemican Podcast is a podcast about the history of Bohemia and Moravia including travel tips. Hosts Travis Dow and Pete Coleman are Americans living and working in Prague. Bohemican RSS","title":"Bohemican Podcast"},{"location":"posts/trents-favorite-podcasts/#hardcore-history_2","text":"HardCore History is an exciting and entertaining history podcast, mostly focusing on war. HardCore History RSS","title":"HardCore History"},{"location":"posts/trents-favorite-podcasts/#history-of-germany_2","text":"Unfortunately, the History Of Germany podcast, hosted by Travis Dow, suffers from sputtery repitition, um you know sputtery repitition, circular explanations, so like circular explanations, and some minor valley talk and vocal fry issues, so um like yeah. But the subject target is rich, exhaustive, and comprehensive, and covers the history of both Germany and Germanic peoples and tribes beginning with the Neanderthals. History Of Germany RSS","title":"History Of Germany"},{"location":"posts/trents-favorite-podcasts/#in-our-time-history_1","text":"In Our Time: History is a lively and argumentative history conversation betwixt a revolving round-table of academic historians. In Our Time: History RSS","title":"In Our Time: History"},{"location":"posts/trents-favorite-podcasts/#the-history-of-ancient-greece_2","text":"Unfortunately, The History Of Ancient Greece lacks expressiveness and poetic phrasing. Your Bible-reading Grandpa's lyrical, King's-English word-smithing this is not. However, the abundance of content is thorough and exhaustive. The History Of Ancient Greece RSS","title":"The History Of Ancient Greece"},{"location":"posts/trents-favorite-podcasts/#wittenberg-to-westphalia_5","text":"Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS","title":"Wittenberg To Westphalia"},{"location":"posts/trents-favorite-podcasts/#youre-dead-to-me_2","text":"You're Dead To Me , is a fun and serious history and humour podcast featuring a different academic historian, and a different professional comic personality each episode. You're Dead To Me RSS Categories","title":"You're Dead To Me"},{"location":"posts/trents-favorite-podcasts/#home-lab","text":"","title":"Home Lab"},{"location":"posts/trents-favorite-podcasts/#self-hosted_1","text":"Self-Hosted is a podcast about self-hosting, homelabs, and IOT. Self-Hosted RSS Categories","title":"Self-Hosted"},{"location":"posts/trents-favorite-podcasts/#internet-of-things","text":"","title":"Internet of Things"},{"location":"posts/trents-favorite-podcasts/#minipc_1","text":"MiniPC is a round-table podcast about SBC computers, such as the RaspberryPI, as well as IOT. MiniPC RSS","title":"MiniPC"},{"location":"posts/trents-favorite-podcasts/#self-hosted_2","text":"Self-Hosted is a podcast about self-hosting, homelabs, and IOT. Self-Hosted RSS","title":"Self-Hosted"},{"location":"posts/trents-favorite-podcasts/#self-hosted_3","text":"Self-Hosted is a podcast about self-hosting, homelabs, and IOT. Self-Hosted RSS Categories","title":"Self-Hosted"},{"location":"posts/trents-favorite-podcasts/#java","text":"","title":"Java"},{"location":"posts/trents-favorite-podcasts/#talking-kotlin_3","text":"Talking Kotlin is a podcast about the Kotlin Programming Language , which is the default programming language for Android Applications. Both kotlin and the podcast are created by JetBrains , which is a company that creates and sells professional IDE's, from which Android Studio is derived. Talking Kotlin RSS Categories","title":"Talking Kotlin"},{"location":"posts/trents-favorite-podcasts/#javascript","text":"","title":"Javascript"},{"location":"posts/trents-favorite-podcasts/#react-podcast_2","text":"React Podcast is a podcast about javascript web development with react . React Podcast RSS","title":"React Podcast"},{"location":"posts/trents-favorite-podcasts/#syntax_2","text":"Syntax is a podcast about web development. Syntax RSS Categories","title":"Syntax"},{"location":"posts/trents-favorite-podcasts/#kotlin","text":"","title":"Kotlin"},{"location":"posts/trents-favorite-podcasts/#talking-kotlin_4","text":"Talking Kotlin is a podcast about the Kotlin Programming Language , which is the default programming language for Android Applications. Both kotlin and the podcast are created by JetBrains , which is a company that creates and sells professional IDE's, from which Android Studio is derived. Talking Kotlin RSS Categories","title":"Talking Kotlin"},{"location":"posts/trents-favorite-podcasts/#linux","text":"","title":"Linux"},{"location":"posts/trents-favorite-podcasts/#ask-noah-show_1","text":"Ask Noah Show is a Radio Callin Show about Linux and Open Source. Host Noah Chelliah operates AltiSpeed , an IT company in Fargo. Ask Noah Show RSS","title":"Ask Noah Show"},{"location":"posts/trents-favorite-podcasts/#destination-linux_1","text":"Destination Linux is a round-table podcast about Linux. Destination Linux RSS","title":"Destination Linux"},{"location":"posts/trents-favorite-podcasts/#gnu-world-order_1","text":"GNU World Order is a podcast about Linux, especially Slackware. GNU World Order RSS","title":"GNU World Order"},{"location":"posts/trents-favorite-podcasts/#late-night-linux_1","text":"Late Night Linux is a round-table podcast of exquisite production value, about Linux. Late Night Linux RSS","title":"Late Night Linux"},{"location":"posts/trents-favorite-podcasts/#linux-action-news_1","text":"Linux Action News is a weekly podcast of Linux News. Linux Action News RSS","title":"Linux Action News"},{"location":"posts/trents-favorite-podcasts/#linux-inlaws_1","text":"Linux InLaws ist ein bier-sodden Podcast \u00fcber Linux. Linux InLaws RSS","title":"Linux InLaws"},{"location":"posts/trents-favorite-podcasts/#linux-unplugged_1","text":"Linux Unplugged is a podcast about Linux with live audience participation via mumble. Linux Unplugged RSS","title":"Linux Unplugged"},{"location":"posts/trents-favorite-podcasts/#minipc_2","text":"MiniPC is a round-table podcast about SBC computers, such as the RaspberryPI, as well as IOT. MiniPC RSS","title":"MiniPC"},{"location":"posts/trents-favorite-podcasts/#mintcast_1","text":"MintCast is a long-running round-table podcast by the Linux Mint community for all users of Linux. MintCast RSS","title":"MintCast"},{"location":"posts/trents-favorite-podcasts/#self-hosted_4","text":"Self-Hosted is a podcast about self-hosting, homelabs, and IOT. Self-Hosted RSS","title":"Self-Hosted"},{"location":"posts/trents-favorite-podcasts/#sunday-morning-linux-review_1","text":"Sunday Morning Linux Review is an occassional round-table podcast about Linux. Sunday Morning Linux Review RSS","title":"Sunday Morning Linux Review"},{"location":"posts/trents-favorite-podcasts/#ubuntu-podcast_1","text":"Ubuntu Podcast is a podcast about Linux and Ubuntu , hosted by a round-table of current and former Ubuntu users and employees. Ubuntu Podcast RSS Categories","title":"Ubuntu Podcast"},{"location":"posts/trents-favorite-podcasts/#linux-mint","text":"","title":"Linux Mint"},{"location":"posts/trents-favorite-podcasts/#mintcast_2","text":"MintCast is a long-running round-table podcast by the Linux Mint community for all users of Linux. MintCast RSS Categories","title":"MintCast"},{"location":"posts/trents-favorite-podcasts/#middle-ages","text":"","title":"Middle Ages"},{"location":"posts/trents-favorite-podcasts/#wittenberg-to-westphalia_6","text":"Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories","title":"Wittenberg To Westphalia"},{"location":"posts/trents-favorite-podcasts/#open-source","text":"","title":"Open Source"},{"location":"posts/trents-favorite-podcasts/#ask-noah-show_2","text":"Ask Noah Show is a Radio Callin Show about Linux and Open Source. Host Noah Chelliah operates AltiSpeed , an IT company in Fargo. Ask Noah Show RSS","title":"Ask Noah Show"},{"location":"posts/trents-favorite-podcasts/#floss-weekly_1","text":"FLOSS Weekly is a podcast about free, libre, open-source software. FLOSS Weekly RSS","title":"FLOSS Weekly"},{"location":"posts/trents-favorite-podcasts/#minipc_3","text":"MiniPC is a round-table podcast about SBC computers, such as the RaspberryPI, as well as IOT. MiniPC RSS","title":"MiniPC"},{"location":"posts/trents-favorite-podcasts/#open-source-voices_1","text":"Open Source Voices is an interview podcast featuring open-source personalities, hosted by J.T. Pennington Open Source Voices Categories","title":"Open Source Voices"},{"location":"posts/trents-favorite-podcasts/#politics","text":"","title":"Politics"},{"location":"posts/trents-favorite-podcasts/#no-agenda_2","text":"No Agenda Host Adam Curry is a former MTV Personality from the 80s, and consequently the podcast has a very high production value. Host John C Dvorak is a tech journalist. No Agenda RSS","title":"No Agenda"},{"location":"posts/trents-favorite-podcasts/#scholars-sense_1","text":"Host Victor Davis Hanson is a Classicist and Historian. Scholars & Sense RSS Categories","title":"Scholars &amp; Sense"},{"location":"posts/trents-favorite-podcasts/#python","text":"","title":"Python"},{"location":"posts/trents-favorite-podcasts/#django-chat_3","text":"Django Chat is a two-man team podcast about the development and community of Django , which is an opinionated and structured python web-application framework. Django Chat RSS","title":"Django Chat"},{"location":"posts/trents-favorite-podcasts/#django-riffs_3","text":"Django Riffs is an instructional, tutorial podcast about Django , which is an opinionated and structured python web-application framework. Django Riffs RSS","title":"Django Riffs"},{"location":"posts/trents-favorite-podcasts/#python-bytes_2","text":"Python Bytes is a podcast about Python. Python Bytes RSS","title":"Python Bytes"},{"location":"posts/trents-favorite-podcasts/#talk-python-to-me_2","text":"Talk Python To Me is a podcast about python. Talk Python To Me RSS Categories","title":"Talk Python To Me"},{"location":"posts/trents-favorite-podcasts/#react","text":"","title":"React"},{"location":"posts/trents-favorite-podcasts/#react-podcast_3","text":"React Podcast is a podcast about javascript web development with react . React Podcast RSS Categories","title":"React Podcast"},{"location":"posts/trents-favorite-podcasts/#reformation","text":"","title":"Reformation"},{"location":"posts/trents-favorite-podcasts/#wittenberg-to-westphalia_7","text":"Wittenberg To Westphalia is nominally a podcast about the Wars of the Reformation , Wittenberg being the city where Martin Luther began the Protestant Reformation in 1517, and Westphalia being where the end of the 30-Years-War was negotiated in 1648. But in reality this podcast exhaustively covers European History from the Fall of The Roman Empire onward, with much prologue and tangent. This is a really awesome podcast. Wittenberg To Westphalia RSS Categories","title":"Wittenberg To Westphalia"},{"location":"posts/trents-favorite-podcasts/#slackware","text":"","title":"Slackware"},{"location":"posts/trents-favorite-podcasts/#gnu-world-order_2","text":"GNU World Order is a podcast about Linux, especially Slackware. GNU World Order RSS Categories","title":"GNU World Order"},{"location":"posts/trents-favorite-podcasts/#small-board-computers","text":"","title":"Small Board Computers"},{"location":"posts/trents-favorite-podcasts/#minipc_4","text":"MiniPC is a round-table podcast about SBC computers, such as the RaspberryPI, as well as IOT. MiniPC RSS Categories","title":"MiniPC"},{"location":"posts/trents-favorite-podcasts/#sysadmin","text":"","title":"SysAdmin"},{"location":"posts/trents-favorite-podcasts/#25-admins_1","text":"Host Joe Ressington is a podcast production savant. Jim Salter is a SysAdmin and tech journalist and a developer of ZFS utilities. Allan Jude is a FreeBSD developer who operates a video streaming service called Scale Engine . 2.5 Admins RSS","title":"2.5 Admins"},{"location":"posts/trents-favorite-podcasts/#ask-noah-show_3","text":"Ask Noah Show is a Radio Callin Show about Linux and Open Source. Host Noah Chelliah operates AltiSpeed , an IT company in Fargo. Ask Noah Show RSS Categories","title":"Ask Noah Show"},{"location":"posts/trents-favorite-podcasts/#travel","text":"","title":"Travel"},{"location":"posts/trents-favorite-podcasts/#bohemican-podcast_3","text":"Bohemican Podcast is a podcast about the history of Bohemia and Moravia including travel tips. Hosts Travis Dow and Pete Coleman are Americans living and working in Prague. Bohemican RSS Categories","title":"Bohemican Podcast"},{"location":"posts/trents-favorite-podcasts/#tutorial","text":"","title":"Tutorial"},{"location":"posts/trents-favorite-podcasts/#django-riffs_4","text":"Django Riffs is an instructional, tutorial podcast about Django , which is an opinionated and structured python web-application framework. Django Riffs RSS Categories","title":"Django Riffs"},{"location":"posts/trents-favorite-podcasts/#ubuntu","text":"","title":"Ubuntu"},{"location":"posts/trents-favorite-podcasts/#ubuntu-podcast_2","text":"Ubuntu Podcast is a podcast about Linux and Ubuntu , hosted by a round-table of current and former Ubuntu users and employees. Ubuntu Podcast RSS Categories","title":"Ubuntu Podcast"},{"location":"posts/trents-favorite-podcasts/#web","text":"","title":"Web"},{"location":"posts/trents-favorite-podcasts/#django-chat_4","text":"Django Chat is a two-man team podcast about the development and community of Django , which is an opinionated and structured python web-application framework. Django Chat RSS","title":"Django Chat"},{"location":"posts/trents-favorite-podcasts/#its-all-widgets-flutter-podcast_4","text":"It's All Widgets Flutter Podcast is a podcast about Flutter . It's All Widgets Flutter Podcast RSS","title":"It's All Widgets Flutter Podcast"},{"location":"posts/trents-favorite-podcasts/#react-podcast_4","text":"React Podcast is a podcast about javascript web development with react . React Podcast RSS","title":"React Podcast"},{"location":"posts/trents-favorite-podcasts/#syntax_3","text":"Syntax is a podcast about web development. Syntax RSS Categories","title":"Syntax"},{"location":"posts/trents-favorite-podcasts/#rss-feeds","text":"2.5 Admins RSS Ask Noah Show RSS Bohemican RSS Coder Radio RSS Destination Linux RSS Django Chat RSS Django Riffs RSS Engines Of Our Ingenuity FLOSS Weekly RSS GNU World Order RSS HardCore History RSS History Of Germany RSS In Our Time: History RSS It's All Widgets Flutter Podcast RSS Late Night Linux RSS Linux Action News RSS Linux InLaws RSS Linux Unplugged RSS MiniPC RSS MintCast RSS No Agenda RSS Open Source Voices Python Bytes RSS React Podcast RSS Scholars & Sense RSS Self-Hosted RSS Sunday Morning Linux Review RSS Syntax RSS Talk Python To Me RSS Talking Kotlin RSS The History Of Ancient Greece RSS The Matt Freire Show RSS The Mike Dominick Show RSS Ubuntu Podcast RSS Wittenberg To Westphalia RSS You're Dead To Me RSS Categories","title":"RSS Feeds"},{"location":"posts/xmpp-apt-notifications/","text":"date: 2021-01-09 Introduction In order to save yourself the work of checking your computer for updates, configure it to send you a weekly notification for updates using cron and sendxmpp. Register an Xmpp User ssh into your prosody server and use prosodyctl to create a user for your computer. i.e. for your htpc: # prosodyctl adduser htpc@example.com You will be prompted to create a password. Install sendxmpp ssh into your computer and install sendxmpp . i.e. for your htpc: $ sudo apt-get install sendxmpp Configure sendxmpp ssh into your computer and login as the root user using $ sudo su write the following contents into /root/.sendxmpprc , i.e. for your htpc htpc@example.com;example.com <password> secure your .sendxmpprc file by making it read-only, and only accessible by the root user # chmod 600 /root/.sendxmpprc Create Cron Job While still logged in as root, open crontab for editing. # crontab -e And then write a command in crontab , i.e. for your htpc. #!/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 15 3 * * 4 apt-get update && apt-get -u upgrade --assume-no | sendxmpp -t -u htpc <yourself>@example.com Receive Notifications on Android Device Example Notification in Yaxim on Android","title":"XMPP Apt Notification"},{"location":"posts/xmpp-apt-notifications/#introduction","text":"In order to save yourself the work of checking your computer for updates, configure it to send you a weekly notification for updates using cron and sendxmpp.","title":"Introduction"},{"location":"posts/xmpp-apt-notifications/#register-an-xmpp-user","text":"ssh into your prosody server and use prosodyctl to create a user for your computer. i.e. for your htpc: # prosodyctl adduser htpc@example.com You will be prompted to create a password.","title":"Register an Xmpp User"},{"location":"posts/xmpp-apt-notifications/#install-sendxmpp","text":"ssh into your computer and install sendxmpp . i.e. for your htpc: $ sudo apt-get install sendxmpp","title":"Install sendxmpp"},{"location":"posts/xmpp-apt-notifications/#configure-sendxmpp","text":"ssh into your computer and login as the root user using $ sudo su write the following contents into /root/.sendxmpprc , i.e. for your htpc htpc@example.com;example.com <password> secure your .sendxmpprc file by making it read-only, and only accessible by the root user # chmod 600 /root/.sendxmpprc","title":"Configure sendxmpp"},{"location":"posts/xmpp-apt-notifications/#create-cron-job","text":"While still logged in as root, open crontab for editing. # crontab -e And then write a command in crontab , i.e. for your htpc. #!/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 15 3 * * 4 apt-get update && apt-get -u upgrade --assume-no | sendxmpp -t -u htpc <yourself>@example.com","title":"Create Cron Job"},{"location":"posts/xmpp-apt-notifications/#receive-notifications-on-android-device","text":"Example Notification in Yaxim on Android","title":"Receive Notifications on Android Device"}]}