Tested the script in a relatively clean install of R4.2.4: it failed with « Go installation failed».
Looking at the running sys-blocky left by the failed install - it does not have an IP address, so the wget attempt to download the Go tarball obviously fails…
Thanks for the guide! This looks promising. Since the script assigns NETVM="sys-net", I assume the setup will be appVM -> sys-firewall -> sys-blocky -> sys-net. I’d also be interested in filtering DNS traffic that is tunneled through each of my sys-vpn’s, so presumably one would need an additional upstream sys-blocky VM for each sys-vpn.
I wonder if it would be possible to create an off-chain sys-blocky DNS qube (similar to having pi-hole on a separate raspberry pi device) to filter traffic using qvm-connect-tcp? I’m still baffled by DNS in Qubes, so forgive me if this question is impossibly naive… I was able to prototype something along these lines with opensnitch nodes, but opensnitch implements a client-server model to enable this possibility.
The script does not work, there are missing networking connection to install go. To change that, correct the following lines:
Line 72:
qvm-run -u root “$CLONED_TEMPLATE” “apt update && apt install -y qubes-core-agent-networking git wget” || {
Line 81:
if ! qvm-run -p “$CLONED_TEMPLATE” “dpkg -l qubes-core-agent-networking git wget” >/dev/null 2>&1; then
Sorry for not answering earlier. I made another script and tested it on a fresh installation of Qubes 4.2. The script creates two VMs: one for data and one for DNS. You just need to run it, and the script will ask you to name the VMs. Then it will do all the work.
#!/bin/bash
# Configuration
BASE_CLONED_TEMPLATE="d12m-datagate"
DATAKEEPER=$1
GATEKEEPER=$1
DATAKEEPER_IP=$1
GATEKEEPER_IP=$1
TEMPNET=$1
MINIMAL_TEMPLATE="debian-12-minimal"
LOG_FILE="/var/log/qubes-blocky-install-beta-script$(date +%Y%m%d).log"
separador() {
echo "----------------------------------------"
}
# ESSENTIAL FUNCTIONS
verify_qubes_tools() {
for cmd in qvm-ls qvm-run qvm-template qvm-clone qvm-create qvm-prefs qvm-shutdown qvm-kill; do
if ! command -v "$cmd" >/dev/null; then
echo "ERROR: Qubes command not found: $cmd"
exit 1
fi
done
}
template_exists() {
qvm-ls | grep -wq "$1"
}
vm_exists() {
qvm-ls | grep -wq "^$1"
}
run_in_vm() {
local vm=$1 cmd=$2
echo "[$vm] Executing: $cmd"
if ! qvm-run -u root --pass-io "$vm" "$cmd"; then
echo "ERROR: Command failed in $vm: $cmd"
return 1
fi
return 0
}
shutdown_vm() {
local vm=$1
if qvm-ls | grep -wq "$vm" && qvm-check --running "$vm"; then
echo "Shutting down $vm..."
qvm-shutdown "$vm" --wait || {
echo "WARNING: Could not shutdown $vm normally, forcing..."
qvm-kill "$vm"
}
fi
}
shutdown_template() {
local template=$1
if qvm-ls | grep -wq "$template" && qvm-check --running "$template"; then
echo "Shutting down $template..."
qvm-shutdown "$vm" --wait || {
echo "WARNING: Could not shutdown $template normally, forcing..."
qvm-kill "$vm"
}
fi
}
function inject_extrepo {
local target=$1
run_in_vm "$target" "bash -c '
set -e
rm -rf /etc/apt/sources.list.d/extrepo_librewolf.sources
touch /etc/apt/sources.list.d/extrepo_librewolf.sources
cat > /etc/apt/sources.list.d/extrepo_librewolf.sources <<\"EOF\"
Types: deb
Uris: https://repo.librewolf.net
Suites: librewolf
Architectures: amd64 arm64
Components: main
Signed-By: /var/lib/extrepo/keys/librewolf.asc
EOF
'"
run_in_vm "$target" "bash -c '
set -e
sudo mkdir -p /var/lib/extrepo/keys
rm -rf /var/lib/extrepo/keys/librewolf.asc
touch /var/lib/extrepo/keys/librewolf.asc
cat > /var/lib/extrepo/keys/librewolf.asc <<\"EOF\"
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
=tZgh
-----END PGP PUBLIC KEY BLOCK-----
EOF
'"
}
function fix_locale() {
local target=$1
local LOCALE_CONTENT="LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=en_US.UTF-8"
run_in_vm "$target" "echo "$LOCALE_CONTENT" | sudo tee /etc/default/locale" > /dev/null
run_in_vm "$target" "sudo sed -i 's/^# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen"
run_in_vm "$target" "sudo locale-gen"
echo "Configuração de locale concluída."
}
function check_vm {
local target="$1"
if qvm-ls | grep -wq "$target" && qvm-check --running "$target"; then
echo "Shutting down $target..."
qvm-shutdown "$target" --wait || {
echo "WARNING: Could not shutdown $target normally, forcing..."
qvm-kill "$target"
}
fi
}
function resize {
local target=$1
local volume_size=$2
if qvm-ls --raw-list | grep -wq "$target"; then
echo "Booting $target.."
qvm-start "$target"
while ! qvm-check --running "$target" 2>/dev/null; do
echo "Waiting $target finish boot..."
sleep 1
done
echo "Redimensionando o volume de $target para $volume_size..."
qvm-volume resize $target:private $volume_size
else
echo "$target nao existe. Abortando!"
fi
shutdown_vm $target
}
function fnl {
local vmdata="$DATAKEEPER"
local vmgate="$GATEKEEPER"
if qvm-ls | grep -wq "$vmdata" && qvm-check --running "$vmdata"; then
echo "Shutting down $vmdata..."
qvm-shutdown "$vmdata" --wait || {
echo "WARNING: Could not shutdown $vmdata normally, forcing..."
qvm-kill "$vmdata"
}
fi
if qvm-ls | grep -wq "$vmgate" && qvm-check --running "$vmgate"; then
echo "Shutting down $vmgate..."
qvm-shutdown "$vmgate" --wait || {
echo "WARNING: Could not shutdown $vmgate normally, forcing..."
qvm-kill "$vmgate"
}
fi
qvm-prefs "$vmdata" netvm "$vmgate"
echo ""
echo "$GATEKEEPER [ok]"
echo "$DATAKEEPER [ok]"
echo""
separador
echo "Installation completed!"
separador
echo ""
echo ""
echo ">>>>>>> IMPORTANT <<<<<<<"
echo ""
separador
echo "INSIDE GRAFANA PANEL"
echo "go to: Connections -> Data sources"
echo "Add data source -> MySQL"
separador
echo "Host URL: localhost:3306"
echo "Database: blocky"
echo "Username: mysql-user"
echo "Password: mysql-pass (you can change it later)"
echo "Click on: Save & test"
separador
echo "Inside grafana panel after adding your database:"
echo "Click [+] icon on top right of grafana panel"
echo "Then select: Import dashboard"
echo "Type the ID: 14980"
echo "click on LOAD button"
echo "Select your mysql data source and click import!"
separador
echo""
read -p ">> Press enter << to continue and follow the steps above!"
echo ""
echo "Opening grafana panel at localhost:3000, use admin:admin to login!"
qvm-run -q -a "$DATAKEEPER" "librewolf localhost:3000"
}
function getip {
local target=$1
IP=$(qvm-run -u root -p "$target" "qubesdb-read /qubes-ip 2>/dev/null")
if [ -z "$IP" ]; then
IP=$(qvm-run -p "$target" "ip -4 addr show eth0 | grep -oP '(?<=inet\s)\d+\.\d+\.\d+\.\d+' | head -n 1")
fi
}
function blocky_stage_01 {
local vm="$GATEKEEPER"
local go_version="1.24.2"
qvm-run -u root "$vm" "bash -c '
wget -q https://go.dev/dl/go${go_version}.linux-amd64.tar.gz -O /tmp/go.tar.gz &&
tar -C /usr/local -xzf /tmp/go.tar.gz &&
echo \"export PATH=\\\$PATH:/usr/local/go/bin\" >> /etc/profile &&
echo \"export PATH=\\\$PATH:/usr/local/go/bin\" >> /home/user/.bashrc &&
rm -f /tmp/go.tar.gz
'" || {
echo "[$vm] Failed to install Go."
return 1
}
}
function blocky_stage_02 {
local vm="$GATEKEEPER"
local go_version="1.24.2"
local blocky_repo="https://github.com/0xERR0R/blocky.git"
local blocky_dest="/opt/blocky"
local blocky_bin="/usr/local/bin/blocky"
echo "[$vm] Detecting architecture.."
local arch
arch=$(qvm-run -p "$vm" "uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/;s/armv7l/armv7/'") || {
echo "Failed to detect architecture"
return 1
}
echo "[$vm] Compiling Blocky..."
run_in_vm "$vm" "bash -c '
set -e
sudo rm -rf \"$blocky_dest\"
sudo git clone --depth 1 \"$blocky_repo\" \"$blocky_dest\"
cd \"$blocky_dest\"
sudo /usr/local/go/bin/go build \
-ldflags=\"-X '\''github.com/0xERR0R/blocky/util.Version=$go_version'\'' \
-X '\''github.com/0xERR0R/blocky/util.BuildTime=\$(date +%Y-%m-%dT%H:%M:%SZ)'\'' \
-X '\''github.com/0xERR0R/blocky/util.Architecture=$arch'\''\" \
-o \"$blocky_bin\"
sudo chmod +x \"$blocky_bin\"
'"
return 0
}
function blocky_stage_03 {
local target="$GATEKEEPER"
local DATA_IP=$(qvm-run -u root -p "$DATAKEEPER" "qubesdb-read /qubes-ip 2>/dev/null")
local directory="/etc/blocky"
local configfile="/etc/blocky/config.yml"
run_in_vm $target "mkdir -p /etc/blocky"
run_in_vm $target "touch /etc/blocky/local-blacklist.txt"
run_in_vm $target "touch /etc/blocky/config.yml"
run_in_vm $target "bash -c '
cat > /etc/blocky/config.yml <<\"EOF\"
upstreams:
groups:
default:
- 46.227.67.134 # OVPN upstream
- 192.165.9.158 # OVPN upstream
blocking:
denylists:
ads:
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
- https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/tif.medium.txt
- https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
- https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt
- https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/nsfw.txt
- https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/popupads.txt
custom:
- file:///etc/blocky/local-blacklist.txt
clientGroupsBlock:
default:
- ads
- custom
blockType: zeroIP
blockTTL: 6h
ports:
dns: 53
http: 4000
prometheus:
enable: true
path: /metrics
queryLog:
type: mysql
target: mysql-user:mysql-pass@tcp(${DATA_IP}:3306)/blocky?charset=utf8mb4&parseTime=True&loc=Local
logRetentionDays: 30
flushInterval: 60s
log:
level: info
format: text
timestamp: true
caching:
minTime: 5m
maxTime: 30m
EOF
'"
}
function blocky_stage_04 {
local vm="$GATEKEEPER"
qvm-run -u root -p "$vm" "bash -c '
set -e
rm -rf /etc/systemd/system/blocky.service
touch /etc/systemd/system/blocky.service
cat > /etc/systemd/system/blocky.service <<\"EOF\"
[Unit]
Description=Blocky DNS
After=network.target
[Service]
ExecStart=/usr/local/bin/blocky --config /etc/blocky/config.yml
Restart=always
User=root
[Install]
WantedBy=multi-user.target
EOF
'"
}
function prometheus_stage_01 {
local target="$DATAKEEPER"
echo "Installing prometheus on $DATAKEEPER"
URL="https://github.com/prometheus/prometheus/releases/download/v3.3.0-rc.1/prometheus-3.3.0-rc.1.linux-amd64.tar.gz"
TEMP_DIR="/tmp"
DEST_DIR="/etc/prometheus"
FILE_NAME="prometheus-3.3.0-rc.1.linux-amd64.tar.gz"
EXTRACTED_DIR="prometheus-3.3.0-rc.1.linux-amd64"
run_in_vm $target "wget -q -O $TEMP_DIR/$FILE_NAME $URL" || {
echo "ERROR: [$target] Failed to download prometheus"
exit 1
}
if [ $? -ne 0 ]; then
echo "Falha ao baixar o arquivo. Verifique a URL e a conexão com a internet."
exit 1
fi
run_in_vm $target "sudo mkdir -p $DEST_DIR"
run_in_vm $target "sudo tar -xzf $TEMP_DIR/$FILE_NAME -C $TEMP_DIR"
if [ $? -ne 0 ]; then
echo "Falha ao descompactar o arquivo."
exit 1
fi
run_in_vm $target "sudo mv $TEMP_DIR/$EXTRACTED_DIR/* $DEST_DIR/"
run_in_vm $target "sudo rm -rf $TEMP_DIR/$EXTRACTED_DIR"
run_in_vm $target "rm $TEMP_DIR/$FILE_NAME"
}
function prometheus_stage_02 {
local target="$DATAKEEPER"
run_in_vm $target "rm -rf /etc/systemd/system/prometheus.service"
run_in_vm $target "touch /etc/systemd/system/prometheus.service"
run_in_vm $target "bash -c '
cat > /etc/systemd/system/prometheus.service <<\"EOF\"
[Unit]
Description=Prometheus
After=network.target
[Service]
ExecStart=/etc/prometheus/prometheus --config.file=/etc/prometheus/prometheus.yml
Restart=always
User=root
[Install]
WantedBy=multi-user.target
EOF
'" || {
echo "ERROR: [$target] Failed to create prometheus service."
exit 1
}
}
function prometheus_stage_03 {
local target="$DATAKEEPER"
local IP=$(qvm-run -u root -p "$GATEKEEPER" "qubesdb-read /qubes-ip 2>/dev/null")
run_in_vm $target "bash -c '
rm -rf /etc/prometheus/prometheus.yml
touch /etc/prometheus/prometheus.yml
cat > /etc/prometheus/prometheus.yml <<\"EOF\"
scrape_configs:
- job_name: 'blocky'
scrape_interval: 90s
metrics_path: '/metrics'
static_configs:
- targets: [${IP}:4000]
relabel_configs:
- source_labels: [__address__]
target_label: instance
replacement: 'METRICS'
EOF
'"
}
function mdb01 {
local target="$DATAKEEPER"
local FILE_PATH="/etc/mysql/mariadb.conf.d/50-server.cnf"
local DESTINATION_DIR="/home/user/"
local TIMESTAMP=$(date +"%Y%m%d_%H%M%S") # Formato de data e hora
# Executa o comando na m__quina virtual usando qvm-run
run_in_vm "$target" "bash -c '
set -e
if [ -f \"$FILE_PATH\" ]; then
mv \"$FILE_PATH\" \"${DESTINATION_DIR}50-server.cnf.old.$TIMESTAMP\"
echo \"INFO: Moved $FILE_PATH to ${DESTINATION_DIR}50-server.cnf.old.$TIMESTAMP\"
else
echo \"INFO: No file found\"
fi
touch \"$FILE_PATH\"
cat > \"$FILE_PATH\" << \"EOF\"
[server]
[mysqld]
max_allowed_packet = 64M
wait_timeout = 28800
interactive_timeout = 28800
#default-time-zone = America/Fortaleza
pid-file = /run/mysqld/mysqld.pid
basedir = /usr
skip-name-resolve
bind-address = 0.0.0.0
max_connections = 100
innodb_buffer_pool_size = 256M # ajusta conforme memoria disponivel
innodb_log_file_size = 64M
innodb_flush_log_at_trx_commit = 2 # Melhor performance com risco minimo
[embedded]
[mariadb]
[mariadb-10.11]
EOF'"
run_in_vm $target "sudo systemctl restart mariadb"
}
function mdb02 {
local GATE_IP=$(qvm-run -u root -p "$GATEKEEPER" "qubesdb-read /qubes-ip 2>/dev/null")
local target="$DATAKEEPER"
local user="mysql-user"
local passwd="mysql-pass"
local host="127.0.0.1"
run_in_vm "$target" "sudo mysql -e 'SHOW DATABASES;'"
run_in_vm "$target" "sudo mysql -e 'CREATE DATABASE blocky CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'"
run_in_vm "$target" "sudo mysql -e \"CREATE USER '${user}'@'${GATE_IP}' IDENTIFIED BY '${passwd}';\""
run_in_vm "$target" "sudo mysql -e \"GRANT ALL PRIVILEGES ON *.* TO '${user}'@'${GATE_IP}';\""
run_in_vm "$target" "sudo mysql -e \"FLUSH PRIVILEGES;\""
run_in_vm "$target" "sudo mysql -e \"CREATE USER '${user}'@'${host}' IDENTIFIED BY '${passwd}';\""
run_in_vm "$target" "sudo mysql -e \"GRANT ALL PRIVILEGES ON *.* TO '${user}'@'${host}';\""
run_in_vm "$target" "sudo mysql -e \"FLUSH PRIVILEGES;\""
}
make_templates() {
echo ""
separador
echo "INSTALL v0.2b"
echo "MINIMAL TEMPLATE: $MINIMAL_TEMPLATE"
echo "CLONED TEMPLATE: $BASE_CLONED_TEMPLATE"
separador
echo ""
read -p "Press ENTER to continue..."
_make_base_template
_config_base_template
echo "$BASE_CLONED_TEMPLATE [OK]"
separador
echo ""
}
_make_base_template() {
separador
echo "Starting template installation procedures."
separador
_verify_minimal_template_and_update
_verify_template_to_clone
install_packages_full
}
_config_base_template() {
echo "Installing packages[qubes, essentials and themes]"
install_packages_full
}
_verify_template_to_clone() {
local VM_TEMPLATE="$BASE_CLONED_TEMPLATE"
echo "Verifying templates before cloning..."
if qvm-ls | grep -wq "$VM_TEMPLATE"; then
echo "Template $VM_TEMPLATE already exists. Skipping clone."
else
echo "Cloning $MINIMAL_TEMPLATE to $VM_TEMPLATE"
qvm-clone "$MINIMAL_TEMPLATE" "$VM_TEMPLATE" || {
echo "ERROR: Failed to clone $VM_TEMPLATE"
exit 1
}
fi
}
_verify_minimal_template_and_update() {
local target="$MINIMAL_TEMPLATE"
echo "Verifying if $target exists.."
if template_exists "$target"; then
echo "$template already exists. Skipping installation procedure."
else
echo "Installing $template"
qvm-template install "$template" || {
echo "ERROR: Failed to install $target"
exit 1
}
fi
echo "Updating $target"
_full_update "$target"
}
_full_update() {
local target="$MINIMAL_TEMPLATE"
run_in_vm "$target" "apt update -y" || {
echo "ERROR: Failed to update $target"
exit 1
}
run_in_vm "$target" "apt upgrade -y" || {
echo "ERROR: Failed to update $target"
exit 1
}
run_in_vm "$target" "apt autoremove -y" || {
echo "ERROR: Failed to update $target"
exit 1
}
shutdown_vm "$target"
}
insertdata() {
triplex_tables
}
install_packages_full() {
fix_locale "$BASE_CLONED_TEMPLATE"
inject_extrepo "$BASE_CLONED_TEMPLATE"
_install_qubes_packages
_install_extra_packages
_install_themes_packages
shutdown_vm "$BASE_CLONED_TEMPLATE"
}
_install_qubes_packages() {
local target="$BASE_CLONED_TEMPLATE"
echo "[$target] Installing qubes packages.."
run_in_vm "$target" "sudo apt install qubes-core-agent-passwordless-root qubes-usb-proxy qubes-input-proxy-sender qubes-core-agent-networking qubes-core-agent-dom0-updates qubes-core-agent-network-manager qubes-core-agent-thunar -y" || {
echo "ERROR: [$target] Failed to install QUBES packages."
}
}
_install_extra_packages() {
local target="$BASE_CLONED_TEMPLATE"
echo "[$target] Installing extra pacakges.."
run_in_vm "$target" "sudo apt install tcpdump telnet iftop nmap dnsutils ncat netcat-openbsd git wget xfce4-terminal geany* -y" || {
echo "ERROR: [$target] Failed to install EXTRA TOOLS packages."
exit 1
}
run_in_vm "$target" "sudo apt install librewolf -y" || {
echo "ERROR: [$target] Failed to install librewolf..."
exit 1
}
}
_install_themes_packages() {
local target="$BASE_CLONED_TEMPLATE"
echo "[$target] Installing themes packages.."
run_in_vm "$target" "apt install -y gnome-themes-extra lxappearance yaru-theme-gtk" || {
echo "ERROR: [$target] to install themes packages"
exit 1
}
}
make_keepers() {
_create_gatekeeper_vm
_create_datakeeper_vm
}
_create_gatekeeper_vm() {
local vm_gatekeeper=$1
local vm_template="$BASE_CLONED_TEMPLATE"
local vm_color
local vm_netvm=$3
local vm_mem=1000
local vm_maxmem=2000
local vm_vcpus=2
local vm_size="20GB"
echo -n "What should the gatekeeper VM be called? "
echo ""
read vm_gatekeeper
while true; do
echo -n "Please provide the netvm for [$vm_gatekeeper]. "
read -p "NetVM: " vm_netvm
TEMPNET="$vm_netvm"
if qvm-ls | grep -wq "$vm_netvm"; then
echo "$vm_gatekeeper netvm: $vm_netvm"
break
else
clear
echo "[$vm_netvm] not found! Please enter a valid NetVM."
fi
done
# Validate the netvm before proceeding
if ! qvm-ls --raw-list | grep -qw "$vm_netvm"; then
echo "ERROR: The specified NetVM [$vm_netvm] does not exist. Exiting."
exit 1
fi
echo "Choose a color for [$vm_gatekeeper]:"
echo "1) Blue"
echo "2) Orange"
echo "3) Red"
echo "4) Green"
echo "5) Yellow"
read -p "Enter the number of the desired color: " color_choice
case $color_choice in
1) vm_color="blue" ;;
2) vm_color="orange" ;;
3) vm_color="red" ;;
4) vm_color="green" ;;
5) vm_color="yellow" ;;
*) echo "Invalid choice! Using default color: blue."
vm_color="blue" ;;
esac
check_vm "$vm_template"
check_vm "$vm_gatekeeper"
if qvm-ls | grep -wq "$vm_gatekeeper"; then
echo "VM $vm_gatekeeper already exists."
else
echo "Creating $vm_gatekeeper..."
qvm-create --standalone --label "$vm_color" \
--template "$vm_template" \
--property memory="$vm_mem" \
--property maxmem="$vm_maxmem" \
--property vcpus="$vm_vcpus" \
--property provides_network=true \
--property netvm="$vm_netvm" "$vm_gatekeeper" || {
echo "ERROR: [$vm_gatekeeper] Failed to create VM."
exit 1
}
resize $vm_gatekeeper $vm_size
getip $vm_gatekeeper
echo "[$vm_gatekeeper] Created successfully!"
fi
echo ""
separador
GATEKEEPER="$vm_gatekeeper"
GATEKEEPER_IP=$(qvm-run -u root -p "$GATEKEEPER" "qubesdb-read /qubes-ip 2>/dev/null")
echo "GATEKEEPER: $vm_gatekeeper"
echo "IP: $GATEKEEPER_IP"
echo "NETVM: $vm_netvm"
echo "Storage: $(qvm-volume info $vm_gatekeeper:private | grep -i "size" | awk '{print $2}')"
separador
}
_config_gatekeeper_vm() { #blocky stages blocky_tables
qvm-start "$GATEKEEPER"
blocky_stages
blocky_tables
}
blocky_stages() {
blocky_stage_01
blocky_stage_02
blocky_stage_03
blocky_stage_04
}
blocky_tables() {
local target="$GATEKEEPER"
local firewalld="/rw/config/qubes-firewall.d"
local networkhooksd="/rw/config/network-hooks.d"
local rclocald="/rw/config/rc.local.d"
local file01="/rw/config/qubes-firewall.d/update_nft.nft" #ok
local file02="/rw/config/qubes-firewall.d/update_nft.sh" #ok
local file03="/rw/config/qubes-firewall.d/internalise.sh" #ok
local file04="/rw/config/network-hooks.d/internalise.sh" #ok
local file05="/rw/config/network-hooks.d/update_nft.sh" #ok
local file06="/rw/config/rc.local.d/blocky.rc"
echo "creating tables on $target"
run_in_vm $target "bash -c '
mkdir -p \"$firewalld\"
touch \"$file01\" \"$file02\" \"$file03\"
mkdir -p \"$networkhooksd\"
touch \"$file04\" \"$file05\"
mkdir -p \"$rclocald\"
touch \"$file06\"
'"
echo "update_nft.nft"
run_in_vm $target "bash -c '
cat > \"$file01\" <<\"EOF\"
#!/usr/sbin/nft -f
flush chain qubes dnat-dns
flush chain qubes custom-forward
insert rule qubes custom-forward tcp dport 53 drop
insert rule qubes custom-forward udp dport 53 drop
flush chain qubes custom-input
insert rule qubes custom-input tcp dport 53 accept
insert rule qubes custom-input udp dport 53 accept
insert rule qubes custom-input tcp dport 4000 accept
flush chain qubes dnat-dns
insert rule qubes dnat-dns iifname \"vif*\" tcp dport 53 dnat to 127.0.0.1
insert rule qubes dnat-dns iifname \"vif*\" udp dport 53 dnat to 127.0.0.1
EOF
chmod +x \"$file01\"
'"
echo "update_nft.sh"
run_in_vm $target "bash -c '
cat > \"$file02\" <<\"EOF\"
#!/bin/sh
sudo nft -f /rw/config/qubes-firewall.d/update_nft.nft
EOF
chmod +x \"$file02\"
'"
echo "internalise.sh"
run_in_vm $target "bash -c '
cat > \"$file03\" <<\"EOF\"
#!/bin/sh
find /proc/sys/net/ipv4/conf -name \"vif*\" -exec bash -c \"echo 1 | tee {}/route_localnet\" \\;
EOF
chmod +x \"$file03\"
'"
echo "internalise.sh"
run_in_vm $target "bash -c '
cat > \"$file04\" <<\"EOF\"
#!/bin/sh
find /proc/sys/net/ipv4/conf -name \"vif*\" -exec bash -c \"echo 1 | tee {}/route_localnet\" \\;
EOF
chmod +x \"$file04\"
'"
echo "update_nft.sh"
run_in_vm $target "bash -c '
cat > \"$file05\" <<\"EOF\"
#!/bin/sh
sudo nft -f /rw/config/qubes-firewall.d/update_nft.nft
EOF
chmod +x \"$file05\"
'"
echo "blocky.rc"
run_in_vm $target "bash -c '
cat > \"$file05\" <<\"EOF\"
#!/bin/sh
systemctl unmask blocky.service
systemctl daemon-reload
systemctl enable --now blocky.service
exec /rw/config/qubes-firewall.d/update_nft.sh
EOF
chmod +x \"$file06\"
'"
run_in_vm $target "echo 'exec /rw/config/rc.local.d/blocky.rc' >> /rw/config/rc.local"
}
_create_datakeeper_vm() {
local vm_datakeeper=$1
local vm_netvm="$TEMPNET"
local vm_template="$BASE_CLONED_TEMPLATE"
local vm_color=$3
echo ""
echo "What should the datakeeper VM be called?"
echo -n ""
read vm_datakeeper
echo "Choose a color for [$vm_datakeeper]:"
echo "1) Blue"
echo "2) Orange"
echo "3) Red"
echo "4) Green"
echo "5) Black"
read -p "Enter the number of the desired color: " color_choice
case $color_choice in
1) vm_color="blue" ;;
2) vm_color="orange" ;;
3) vm_color="red" ;;
4) vm_color="green" ;;
5) vm_color="black" ;;
*) echo "Invalid choice! Using default color: blue."
vm_color="blue" ;;
esac
check_vm "$vm_template"
check_vm "$vm_datakeeper"
if qvm-ls | grep -wq "$vm_datakeeper"; then
echo "VM $vm_datakeeper already exists. Skipping creation!"
else
echo "Creating $vm_datakeeper..."
qvm-create --standalone --label "$vm_color" \
--template "$vm_template" \
--property netvm="$vm_netvm" "$vm_datakeeper" || {
echo "ERROR: [$vm_datakeeper] Failed to create VM."
exit 1
}
fi
echo ""
separador
DATAKEEPER="$vm_datakeeper"
DATAKEEPER_IP=$(qvm-run -u root -p "$DATAKEEPER" "qubesdb-read /qubes-ip 2>/dev/null")
echo "DATAKEEPER: $DATAKEEPER"
echo "IP: $DATAKEEPER_IP"
echo "NETVM: $vm_netvm"
echo "Storage: $(qvm-volume info $DATAKEEPER:private | grep -i "size" | awk '{print $2}')"
separador
echo "[$vm_datakeeper] Successfully created!"
}
_config_datakeeper_vm() { # grafana_prometheus_maria data_tables
qvm-start "$DATAKEEPER"
grafana_prometheus_maria
triplex_tables #data_tables
}
grafana_prometheus_maria() {
install_grafana
install_prometheus_stages
install_mariadb
# qvm-prefs "$DATAKEEPER" netvm "$GATEKEEPER"
}
triplex_tables() {
local target="$DATAKEEPER"
local firewalld="/rw/config/qubes-firewall.d"
local rclocald="/rw/config/rc.local.d"
local file01="/rw/config/qubes-firewall.d/update_nft.nft" #ok
local file02="/rw/config/qubes-firewall.d/update_nft.sh" #ok
local file03="/rw/config/rc.local.d/triplex.rc"
local IP_X=$(qvm-run -u root -p "$GATEKEEPER" "qubesdb-read /qubes-ip 2>/dev/null")
echo "creating tables on $target"
run_in_vm $target "mkdir -p $firewalld"
run_in_vm $target "touch $file01 $file02"
run_in_vm $target "mkdir -p $rclocald"
echo "update_nft.nft"
run_in_vm $target "bash -c '
cat > \"$file01\" <<\"EOF\"
#!/usr/sbin/nft -f
add rule qubes custom-input ip saddr \"$IP_X\" accept
EOF
chmod +x \"$file01\"
'"
echo "update_nft.sh"
run_in_vm $target "bash -c '
cat > \"$file02\" <<\"EOF\"
#!/bin/sh
sudo nft -f /rw/config/qubes-firewall.d/update_nft.nft
EOF
chmod +x \"$file02\"
'"
echo "triplex.rc"
run_in_vm $target "bash -c '
cat > \"$file03\" <<\"EOF\"
#!/bin/bash
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus.service
sudo systemctl enable --now mariadb
sudo systemctl enable --now grafana.service
exec /rw/config/qubes-firewall.d/update_nft.sh
EOF
chmod +x \"$file03\"
'"
run_in_vm $target "echo 'exec /rw/config/rc.local.d/triplex.rc' >> /rw/config/rc.local"
# getip "$GATEKEEPER"
# run_in_vm "$DATAKEEPER" "sed -i 's/TARGETACCESS2/$GATEKEEPER_IP/g' /rw/config/qubes-firewall.d/update_nft.nft"
}
install_grafana() {
local localvm="$DATAKEEPER"
local NETVM=$(qvm-prefs "$GATEKEEPER" netvm)
local current_netvm=$(qvm-prefs "$localvm" netvm)
if [ "$current_netvm" != "$NETVM" ]; then
echo "Mudando $localvm netvm para $NETVM."
qvm-prefs "$localvm" netvm "$NETVM"
else
echo "A netvm ja esta configurada como $NETVM."
fi
echo "INFO: Iniciando instalacao do Grafana..."
run_in_vm "$localvm" "sudo apt-get update -y && sudo apt-get install -y apt-transport-https software-properties-common"
run_in_vm "$localvm" "sudo mkdir -p /etc/apt/keyrings/ && sudo wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null && echo 'deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main' | sudo tee /etc/apt/sources.list.d/grafana.list"
run_in_vm "$localvm" "sudo apt-get update -y && sudo apt-get install grafana -y"
run_in_vm "$localvm" "sudo systemctl start grafana-server && sudo systemctl enable grafana-server && sudo grafana-cli plugins install grafana-piechart-panel"
echo "Grafana successfully installed!"
echo "grafana-piechart-panel done!"
}
install_prometheus_stages() {
prometheus_stage_01
prometheus_stage_02
prometheus_stage_03
}
install_mariadb() {
local target="$DATAKEEPER"
run_in_vm $target "sudo apt install mariadb-server -y"
mdb01
mdb02
}
main_execute() {
make_templates
make_keepers
_config_gatekeeper_vm
_config_datakeeper_vm
fnl
}
main_execute