├── files ├── hostname.emx ├── doas.conf ├── sensorsd.conf ├── hostname.bridge0 ├── bogons_mkdb.sh ├── nodeexporter ├── wireguard ├── pf_log_rotate.sh ├── unbound ├── adservers_mkdb.sh └── sysctl.conf ├── .gitignore ├── templates ├── ntpd.conf.j2 ├── hostname.vether0.j2 ├── adservers_permitted.db.j2 ├── hostname.athn0.j2 ├── resolv.conf.j2 ├── hostname.tun0.j2 ├── hostname.pppoe0.j2 ├── tun0.conf.j2 ├── dhcpd.conf.j2 ├── unbound.conf.j2 └── pf.conf.j2 ├── images ├── router.png ├── server.png ├── assemble.png ├── grafana1.png ├── grafana2.png ├── memtest.png ├── bufferbloat.png └── serial_console.png ├── go.sh ├── bootstrap.yml ├── LICENSE ├── settings.yml.example ├── provision.yml ├── README.md └── OpenBSDNodes.json /files/hostname.emx: -------------------------------------------------------------------------------- 1 | up 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.retry 3 | *.private 4 | settings.yml 5 | -------------------------------------------------------------------------------- /files/doas.conf: -------------------------------------------------------------------------------- 1 | permit nopass setenv { ENV PS1 SSH_AUTH_SOCK } :wheel 2 | -------------------------------------------------------------------------------- /templates/ntpd.conf.j2: -------------------------------------------------------------------------------- 1 | servers {{ ntp_pool }} 2 | sensor * 3 | constraints from "https://www.google.com" 4 | -------------------------------------------------------------------------------- /templates/hostname.vether0.j2: -------------------------------------------------------------------------------- 1 | inet {{ network_address }} {{ network_mask }} {{ network_broadcast }} 2 | up 3 | -------------------------------------------------------------------------------- /images/router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinbaillie/homebrew-openbsd-pcengines-router/HEAD/images/router.png -------------------------------------------------------------------------------- /images/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinbaillie/homebrew-openbsd-pcengines-router/HEAD/images/server.png -------------------------------------------------------------------------------- /templates/adservers_permitted.db.j2: -------------------------------------------------------------------------------- 1 | {% for adserver in adservers_permitted %} 2 | {{ adserver }} 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /images/assemble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinbaillie/homebrew-openbsd-pcengines-router/HEAD/images/assemble.png -------------------------------------------------------------------------------- /images/grafana1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinbaillie/homebrew-openbsd-pcengines-router/HEAD/images/grafana1.png -------------------------------------------------------------------------------- /images/grafana2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinbaillie/homebrew-openbsd-pcengines-router/HEAD/images/grafana2.png -------------------------------------------------------------------------------- /images/memtest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinbaillie/homebrew-openbsd-pcengines-router/HEAD/images/memtest.png -------------------------------------------------------------------------------- /images/bufferbloat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinbaillie/homebrew-openbsd-pcengines-router/HEAD/images/bufferbloat.png -------------------------------------------------------------------------------- /images/serial_console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinbaillie/homebrew-openbsd-pcengines-router/HEAD/images/serial_console.png -------------------------------------------------------------------------------- /files/sensorsd.conf: -------------------------------------------------------------------------------- 1 | hw.sensors.km0.temp0:high=72C:command=echo "%2 \( high=%4 \)" \ 2 | | mail -s "`hostname` sensorsd WARNING \(cpu temp\)" root 3 | -------------------------------------------------------------------------------- /templates/hostname.athn0.j2: -------------------------------------------------------------------------------- 1 | media autoselect mode 11n mediaopt hostap chan {{ wireless_chan }} 2 | nwid {{ wireless_ssid }} 3 | wpakey {{ wireless_key }} 4 | down 5 | -------------------------------------------------------------------------------- /templates/resolv.conf.j2: -------------------------------------------------------------------------------- 1 | lookup file bind 2 | {% for nameserver in nameservers %} 3 | nameserver {{ nameserver }} 4 | {% endfor %} 5 | search {{ network_domain }} 6 | -------------------------------------------------------------------------------- /files/hostname.bridge0: -------------------------------------------------------------------------------- 1 | add em1 2 | add em2 3 | add athn0 4 | add vether0 5 | blocknonip em1 6 | blocknonip em2 7 | blocknonip athn0 8 | blocknonip vether0 9 | up 10 | -------------------------------------------------------------------------------- /templates/hostname.tun0.j2: -------------------------------------------------------------------------------- 1 | inet {{ wireguard_address }} {{ wireguard_mask }} 2 | mtu 1492 3 | up 4 | !/sbin/route -q -n add -inet {{ wireguard_cidr }} -iface {{ wireguard_address }} 5 | -------------------------------------------------------------------------------- /files/bogons_mkdb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | DB="/etc/bogons.db" 3 | /usr/local/bin/curl -Ls https://www.team-cymru.org/Services/Bogons/fullbogons-ipv4.txt -o "${DB}" >/dev/null 4 | pfctl -v -t bogons -T replace -f "${DB}" 5 | -------------------------------------------------------------------------------- /files/nodeexporter: -------------------------------------------------------------------------------- 1 | daemon="/usr/bin/node_exporter" 2 | daemon_flags="" 3 | daemon_user=_node-exporter 4 | 5 | . /etc/rc.d/rc.subr 6 | 7 | pexp="${daemon}.*" 8 | rc_bg=YES 9 | rc_reload=NO 10 | 11 | rc_cmd $1 12 | -------------------------------------------------------------------------------- /templates/hostname.pppoe0.j2: -------------------------------------------------------------------------------- 1 | inet 0.0.0.0 255.255.255.255 NONE mtu 1492 \ 2 | pppoedev em0 authproto pap \ 3 | authname '{{ pppoe_authname }}' authkey '{{ pppoe_authkey }}' up 4 | dest 0.0.0.1 5 | !/sbin/route add default -ifp pppoe0 0.0.0.1 6 | -------------------------------------------------------------------------------- /files/wireguard: -------------------------------------------------------------------------------- 1 | daemon="/usr/local/bin/wireguard-go" 2 | 3 | . /etc/rc.d/rc.subr 4 | 5 | rc_reload=NO 6 | 7 | rc_start () { 8 | ${rcexec} "${daemon} tun" || return 1 9 | /usr/local/bin/wg setconf "${daemon_flags}" "/etc/wireguard/${daemon_flags}.conf" 10 | } 11 | 12 | rc_cmd $1 13 | -------------------------------------------------------------------------------- /files/pf_log_rotate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PFLOG=/var/log/pflog 3 | FILE=/var/log/pflog5min.$(date "+%Y%m%d%H%M") 4 | 5 | pkill -ALRM -u root -U root -t - -x pflogd 6 | if [ -r "$PFLOG" ] && [ "$(stat -f %z $PFLOG)" -gt 24 ]; then 7 | mv $PFLOG $FILE 8 | pkill -HUP -u root -U root -t - -x pflogd 9 | tcpdump -n -e -s 160 -ttt -r $FILE | logger -t pf -p local0.info 10 | rm $FILE 11 | fi 12 | -------------------------------------------------------------------------------- /templates/tun0.conf.j2: -------------------------------------------------------------------------------- 1 | [Interface] 2 | PrivateKey = {{ wireguard_private_key_cmd.stdout }} 3 | ListenPort = {{ wireguard_listen_port }} 4 | 5 | {% for peer in wireguard_peers %} 6 | # {{ peer }} 7 | [Peer] 8 | Endpoint = {{ ansible_default_ipv4['address'] }}:{{ wireguard_listen_port }} 9 | PublicKey = {{ wireguard_peers[peer].public_key }} 10 | AllowedIPs = {{ wireguard_peers[peer].allowed_ips }} 11 | 12 | {% endfor %} 13 | -------------------------------------------------------------------------------- /go.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | : "${PYTHON_VERSION:=3.6.4p0}" 4 | 5 | # Bootstrap 6 | #ansible -m raw -c paramiko -u "${USER}" -k \ 7 | #-b --become-method=su --ask-su-pass -a \ 8 | #"PKG_PATH=https://cloudflare.cdn.openbsd.org/pub/OpenBSD/6.3/packages/amd64 \ 9 | #pkg_add python-${PYTHON_VERSION}" -i "$1," "$1" 10 | 11 | ## 1st pass 12 | #ansible-playbook bootstrap.yml -kKi "$1," 13 | 14 | # Main event 15 | ansible-playbook -vvv provision.yml -i "$1," -u "$USER" 16 | -------------------------------------------------------------------------------- /files/unbound: -------------------------------------------------------------------------------- 1 | #!/bin/ksh 2 | # 3 | # $OpenBSD: unbound,v 1.5 2018/01/11 21:09:26 rpe Exp $ 4 | 5 | daemon="/usr/sbin/unbound" 6 | daemon_flags="-c /var/unbound/etc/unbound.conf" 7 | daemon_timeout=720 8 | 9 | . /etc/rc.d/rc.subr 10 | 11 | pexp="unbound${daemon_flags:+ ${daemon_flags}}" 12 | 13 | rc_pre() { 14 | if grep '^[[:space:]]*auto-trust-anchor-file:' \ 15 | /var/unbound/etc/unbound.conf > /dev/null 2>&1; then 16 | /usr/sbin/unbound-anchor -v || true 17 | fi 18 | } 19 | 20 | rc_start() { 21 | ${rcexec} "unbound ${daemon_flags}" 22 | } 23 | 24 | rc_cmd $1 25 | -------------------------------------------------------------------------------- /templates/dhcpd.conf.j2: -------------------------------------------------------------------------------- 1 | option domain-name "{{ network_domain }}"; 2 | option domain-name-servers {{ network_address }}; 3 | 4 | option ntp-servers {{ network_address }}; 5 | 6 | subnet {{ network_subnet }}.0 netmask 255.255.255.0 { 7 | option routers {{ network_address }}; 8 | range {{ network_subnet }}.10 {{ network_subnet }}.127; 9 | } 10 | 11 | {% for client in network_fixed_clients %} 12 | host {{ client }} { 13 | hardware ethernet {{ network_fixed_clients[client].physical }}; 14 | fixed-address {{ network_fixed_clients[client].logical }}; 15 | option host-name "{{ client }}"; 16 | } 17 | 18 | {% endfor %} 19 | -------------------------------------------------------------------------------- /bootstrap.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | become: true 4 | become_method: su 5 | vars: 6 | ansible_python_interpreter: /usr/local/bin/python3 7 | 8 | tasks: 9 | - name: load settings 10 | include_vars: "settings.yml" 11 | 12 | - name: add user public key 13 | authorized_key: user='{{user}}' key="{{item}}" 14 | register: public_key_added 15 | with_file: 16 | - "~/.ssh/id_ed25519.pub" 17 | 18 | - name: configure doas 19 | copy: src='{{dir_files}}/doas.conf' dest='/etc/doas.conf' owner='root' group='wheel' mode='0600' 20 | 21 | - name: ssh standard listening port 22 | lineinfile: dest=/etc/ssh/sshd_config regexp='^#?Port' line='Port 22' 23 | 24 | - name: ssh externally facing listening port 25 | lineinfile: dest=/etc/ssh/sshd_config insertafter='^Port 22' line='Port {{ ssh_external_port }}' 26 | 27 | - name: disable ssh password logins 28 | lineinfile: dest=/etc/ssh/sshd_config regexp="^#?PasswordAuthentication" line="PasswordAuthentication no" 29 | when: public_key_added|success and not public_key_added|skipped 30 | notify: 31 | - restart sshd 32 | 33 | handlers: 34 | - name: restart sshd 35 | service: name=sshd state=restarted 36 | -------------------------------------------------------------------------------- /templates/unbound.conf.j2: -------------------------------------------------------------------------------- 1 | server: 2 | interface: 127.0.0.1 3 | interface: {{ network_address }} 4 | interface: {{ wireguard_address }} 5 | do-ip6: no 6 | prefetch: yes 7 | rrset-roundrobin: yes 8 | 9 | access-control: 0.0.0.0/0 refuse 10 | access-control: ::0/0 refuse 11 | access-control: 127.0.0.0/8 allow 12 | access-control: {{ network_cidr }} allow 13 | access-control: {{ wireguard_cidr }} allow 14 | verbosity: 1 15 | 16 | hide-identity: yes 17 | hide-version: yes 18 | 19 | local-zone: "{{ network_domain }}." transparent 20 | 21 | local-data: "{{ hostname }}.{{ network_domain }}. IN A {{ network_address }}" 22 | local-data-ptr: "{{ network_address }} {{ hostname }}.{{ network_domain }}" 23 | 24 | {% for client in network_fixed_clients %} 25 | local-data: "{{ client }}.{{ network_domain }}. IN A {{ network_fixed_clients[client].logical }}" 26 | local-data-ptr: "{{ network_fixed_clients[client].logical }} {{ client }}.{{ network_domain }}" 27 | 28 | {% endfor %} 29 | include: /etc/adservers.db 30 | 31 | forward-zone: 32 | name: "." 33 | {% for nameserver in nameservers %} 34 | forward-addr: {{ nameserver }} 35 | {% endfor %} 36 | forward-ssl-upstream: yes 37 | 38 | remote-control: 39 | control-enable: yes 40 | control-use-cert: no 41 | control-interface: /var/run/unbound.sock 42 | -------------------------------------------------------------------------------- /files/adservers_mkdb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bash 2 | set -euo pipefail 3 | 4 | : "${CURL:=/usr/local/bin/curl}" 5 | 6 | EXCLUSION_GREP="" 7 | while read -r permitted; do 8 | [ -n "${permitted}" ] && EXCLUSION_GREP="${EXCLUSION_GREP} -e '${permitted}'" 9 | done < /etc/adservers_permitted.db 10 | [ -n "$EXCLUSION_GREP" ] && EXCLUSION_GREP="grep -v -i ${EXCLUSION_GREP}" 11 | 12 | # Populate a new monster DB. 13 | unset LANG && \ 14 | for site in $(${CURL} --fail -s 'https://v.firebog.net/hosts/lists.php?type=tick'); do 15 | ${CURL} --fail -sL "${site}" || :; 16 | done | \ 17 | tr -d '\r' | \ 18 | sed -e 's/^127.0.0.1\s*//g; s/^0.0.0.0\s*//g; s/^0\s*//g; s/localhost//g' \ 19 | -e 's/\s*#.*$//' -e '/^\s*$/d' -e $'s/\r//' | \ 20 | sort | \ 21 | uniq | \ 22 | awk '{print $1}' | \ 23 | sed '/^-/d; /^\./d; /^\s*$/d; /\.\./d' | \ 24 | sort | \ 25 | uniq | \ 26 | eval "${EXCLUSION_GREP}" | \ 27 | { 28 | while read -r site; do 29 | echo -e "local-zone: \"${site}\" refuse"; 30 | done 31 | } > /etc/adservers.db 32 | 33 | # Restart unbound. 34 | chown _unbound:_unbound /etc/adservers.db 35 | pkill -9 -u _unbound unbound 36 | rcctl stop unbound 37 | rcctl start unbound 38 | 39 | # Warm up. 40 | ${CURL} -LIs www.google.com > /dev/null || ${CURL} -LIs www.google.com > /dev/null 41 | -------------------------------------------------------------------------------- /files/sysctl.conf: -------------------------------------------------------------------------------- 1 | ddb.panic=0 # do not enter ddb console on kernel panic, reboot if possible 2 | 3 | kern.bufcachepercent=90 # allow the kernel to use up to 90% of the RAM for cache (default 10%) 4 | kern.maxclusters=128000 # cluster allocation limit 5 | 6 | net.inet.ip.forwarding=1 # permit forwarding (routing) of packets through the firewall 7 | net.inet.ip.ifq.maxlen=768 # maximum allowed output queue length (256*number of physical interfaces) 8 | net.inet.ip.mtudisc=0 # TCP MTU (Maximum Transmission Unit) discovery off since our mss is small enough 9 | net.inet.ip.ttl=254 # the TTL should match what we have for "min-ttl" in scrub rule in pf.conf 10 | 11 | net.inet.tcp.rfc3390=1 # enable RFC3390 TCP window increasing so larger CWND can take affect 12 | net.inet.tcp.mssdflt=1452 # maximum segment size (1452 from scrub pf.conf match statement) 13 | net.inet.tcp.ackonpush=1 # acks for packets with the push bit set should not be delayed 14 | net.inet.tcp.ecn=1 # explicit Congestion Notification enabled 15 | net.inet.tcp.sack=1 # enable TCP Selective ACK (SACK) Packet Recovery 16 | 17 | net.inet.udp.recvspace=262144 # increase UDP "receive" windows size to increase performance 18 | net.inet.udp.sendspace=262144 # increase UDP "send" windows size to increase performance 19 | 20 | net.inet.icmp.errppslimit=1000 # maximum number of outgoing ICMP error messages per second 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Martin Baillie (c) 2017 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Martin Baillie nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /settings.yml.example: -------------------------------------------------------------------------------- 1 | # EXAMPLE settings.yml to feed the Ansible playbooks 2 | user: 3 | full_name: 4 | email: 5 | 6 | gmail_user: 7 | gmail_pass: 8 | 9 | wireless_chan: 10 | wireless_ssid: 11 | wireless_key: 12 | 13 | pppoe_authname: 14 | pppoe_authkey: 15 | 16 | if_out: pppoe0 17 | if_in: vether0 18 | 19 | hostname: 20 | 21 | nameservers: 22 | - 1.1.1.1@853 23 | - 1.0.0.1@853 24 | 25 | network_address: 192.168.1.1 26 | network_subnet: 192.168.1 27 | network_cidr: 192.168.1.0/24 28 | network_mask: 255.255.255.0 29 | network_broadcast: 192.168.1.255 30 | network_domain: a.domain 31 | network_fixed_clients: 32 | a-webserver: 33 | logical: 192.168.1.2 34 | physical: 25:5B:BB:06:F9:84 35 | 36 | a-laptop: 37 | logical: 192.168.1.3 38 | physical: 24:55:b9:06:f9:05 39 | 40 | wireguard_address: 10.0.1.1 41 | wireguard_cidr: 10.0.1.1/24 42 | wireguard_mask: 255.255.255.0 43 | wireguard_listen_port: 51820 44 | wireguard_peers: 45 | a-laptop: 46 | public_key: 47 | allowed_ips: 0.0.0.0/0 48 | 49 | ntp_pool: au.pool.ntp.org 50 | 51 | openbsd_mirror: https://cloudflare.cdn.openbsd.org 52 | openbsd_mirror_directory: openbsd 53 | 54 | dir_files: files 55 | dir_templates: templates 56 | 57 | ssh_external_port: 443 58 | 59 | # Bufferbloat 60 | # Set to about 90-95% of your uplink up/down in megabytes 61 | # https://www.reddit.com/r/openbsd/comments/75ps6h/fqcodel_and_pf/doca4uv/ 62 | # Use DSLreports.com's SpeedTest for Bufferbloat analysis 63 | uplink_upload: 11 64 | uplink_download: 110 65 | 66 | dotfiles_repo: git@github.com:someone/dotfiles.git 67 | dotfiles_links: 68 | - { src: '/home/user/.dotfiles/zsh', dst: '/home/user/.zsh' } 69 | - { src: '/home/user/.dotfiles/vim', dst: '/home/user/.vim' } 70 | - { src: '/home/user/.dotfiles/tmux.conf', dst: '/home/user/.tmux.conf' } 71 | 72 | packages: 73 | - 'curl' 74 | - 'zsh' 75 | - 'git' 76 | - 'pftop' 77 | - 'iperf' 78 | 79 | adservers_permitted: 80 | - 'opensubtitles.org' 81 | - 'algolia.com' 82 | -------------------------------------------------------------------------------- /templates/pf.conf.j2: -------------------------------------------------------------------------------- 1 | # internal interfaces 2 | int_if="{ tun0 vether0 athn0 em1 em2 }" 3 | # egress keyword used to represent the interface with the default route 4 | 5 | # table of bogus private addresses. bogons, martians.. 6 | # db updated weekly by crond 7 | table persist file "/etc/bogons.db" 8 | 9 | # silently drop rejected packets 10 | set block-policy drop 11 | 12 | # packet and byte statistics 13 | set loginterface egress 14 | 15 | # don't filter on the loopback interface 16 | set skip on lo0 17 | 18 | # scrub incoming packets 19 | match in all scrub (no-df random-id) 20 | 21 | # MSS clamping for pppoe 22 | match on egress scrub (max-mss 1452) 23 | 24 | # bufferbloat correction 25 | # NOTE: can no longer use an interface group (i.e. egress) with 'queue' (https://www.openbsd.org/faq/upgrade64.html) 26 | queue outq on {{ if_out }} flows 1024 bandwidth {{ uplink_upload }}M max {{ uplink_upload }}M qlimit 1024 default 27 | #queue inq on {{ if_in }} flows 1024 bandwidth {{ uplink_download }}M max {{ uplink_download }}M qlimit 1024 default 28 | queue inq on {{ if_in }} flows 1024 qlimit 1024 default quantum 300 29 | 30 | # network address translation 31 | match out on egress inet from !(egress:network) to any nat-to (egress:0) 32 | 33 | # quickly drop packets from spoofed or forged ip addresses 34 | antispoof quick for (egress) 35 | 36 | # don't care about ipv6 yet 37 | block return out quick inet6 all 38 | block in quick inet6 all 39 | 40 | # prohibit internet access for device in the bogons table 41 | block in quick on egress from to any 42 | block return out quick on egress from any to 43 | 44 | # default deny all 45 | block all 46 | 47 | # but permit ipv4 traffic from gateway or LAN 48 | pass out quick inet 49 | pass in on $int_if inet 50 | 51 | # prioritise low delay ToS packets 52 | match out on egress set prio (3, 4) 53 | 54 | # prioritise interactive ssh 55 | match out on egress proto tcp to port ssh set prio (3, 5) 56 | 57 | # prioritise icmp 58 | match out on egress proto icmp set prio (6, 7) 59 | 60 | # prioritise dns 61 | match out on egress proto { tcp, udp } to port domain set prio (6, 7) 62 | 63 | # allow ssh connections on chosen external port 64 | pass in on egress inet proto tcp from any to (egress) port {{ ssh_external_port }} 65 | 66 | # allow wireguard connections on chosen external port 67 | pass in on egress inet proto udp from any to (egress) port {{ wireguard_listen_port }} 68 | 69 | # SSH brute force blackhole table 70 | table persist 71 | 72 | # block anything in the brute force table 73 | block quick from 74 | 75 | # append client machine to the table if appears to be brute forcing 76 | pass in on egress proto tcp to (egress) port {{ ssh_external_port }} modulate state \ 77 | (max-src-conn 10, max-src-conn-rate 5/5, \ 78 | overload flush global) 79 | -------------------------------------------------------------------------------- /provision.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | become: true 4 | become_method: doas 5 | vars: 6 | ansible_python_interpreter: /usr/local/bin/python3 7 | 8 | tasks: 9 | - name: load settings 10 | include_vars: "settings.yml" 11 | 12 | - name: set install url 13 | command: echo "{{openbsd_mirror}}/{{openbsd_mirror_directory}}" > /etc/installurl 14 | 15 | - name: packages 16 | openbsd_pkg: name="{{packages}}" state='present' 17 | 18 | - name: install prometheus node_exporter 19 | become: yes 20 | command: go get github.com/prometheus/node_exporter 21 | args: 22 | creates: /usr/bin/node_exporter 23 | environment: 24 | GOPATH: /usr 25 | 26 | - name: add node_exporter user 27 | user: 28 | name: _node-exporter 29 | shell: /sbin/nologin 30 | home: /nonexistent 31 | create_home: false 32 | 33 | - name: copy prometheus node_exporter rc script 34 | copy: src='nodeexporter' dest='/etc/rc.d/' owner='root' group='wheel' mode='a+x' 35 | 36 | # TODO INSTALL wireguard here 37 | 38 | - name: copy wireguard rc script 39 | copy: src='wireguard' dest='/etc/rc.d/' owner='root' group='wheel' mode='a+x' 40 | 41 | - name: wireguard conf directory 42 | file: path='/etc/wireguard' state='directory' owner='root' group='wheel' mode='0640' 43 | 44 | - name: generate wireguard private key 45 | shell: "umask 077; wg genkey > /etc/wireguard/privatekey" 46 | args: 47 | creates: "/etc/wireguard/privatekey" 48 | 49 | - name: register wireguard private key 50 | command: "cat /etc/wireguard/privatekey" 51 | register: wireguard_private_key_cmd 52 | changed_when: false 53 | 54 | - name: compute wireguard public key 55 | shell: "wg pubkey < /etc/wireguard/publickey" 56 | register: "wireguard_public_key_cmd" 57 | changed_when: false 58 | 59 | - name: wireguard conf 60 | template: 61 | src: "tun0.conf.j2" 62 | dest: "/etc/wireguard/tun0.conf" 63 | owner: root 64 | group: wheel 65 | mode: 0640 66 | 67 | - name: update root user full name 68 | command: usermod -c "{{full_name}}" root 69 | 70 | - name: zshell and groups ({{user}}) 71 | user: name='{{user}}' state='present' groups='wheel' shell='/usr/local/bin/zsh' 72 | 73 | - name: zshell (root) 74 | user: name='root' state='present' shell='/usr/local/bin/zsh' 75 | 76 | # Doesn't appear to affect matters 77 | #- name: korn shell vi mode 78 | #lineinfile: dest='/etc/ksh.kshrc' regexp='^(.*)emacs(.*)$' line='\1vi\2' backrefs=yes 79 | 80 | - name: default editor ({{user}}) 81 | become_user: "{{user}}" 82 | blockinfile: 83 | dest: "/home/{{user}}/.zshrc" 84 | create: 'yes' 85 | block: | 86 | alias vi=vim 87 | EDITOR=vi 88 | VISUAL=$EDITOR 89 | export EDITOR VISUAL 90 | 91 | - name: default editor (root) 92 | blockinfile: 93 | dest: "/root/.zshrc" 94 | create: 'yes' 95 | block: | 96 | alias vi=vim 97 | EDITOR=vi 98 | VISUAL=$EDITOR 99 | export EDITOR VISUAL 100 | 101 | - name: ntp servers 102 | template: src='ntpd.conf.j2' dest='/etc/ntpd.conf' owner='root' group='wheel' mode='0644' 103 | 104 | - name: set date from remote 105 | command: rdate -nv {{ntp_pool}} 106 | 107 | - name: clone dotfiles ({{user}}) 108 | become_user: "{{user}}" 109 | git: 110 | repo: "{{dotfiles_repo}}" 111 | dest: "/home/{{user}}/.dotfiles" 112 | recursive: no 113 | accept_hostkey: yes 114 | depth: 1 115 | 116 | - name: link dotfiles into home folder 117 | become_user: "{{user}}" 118 | file: 119 | src: "{{item.src}}" 120 | dest: "{{item.dst}}" 121 | state: link 122 | with_items: "{{dotfiles_links}}" 123 | 124 | - name: mail secrets file 125 | lineinfile: dest='/etc/mail/secrets' line='googlemail {{gmail_user}}:{{gmail_pass}}' create='yes' owner='root' group='_smtpd' mode='0640' 126 | 127 | - name: mail secrets db 128 | command: makemap /etc/mail/secrets 129 | 130 | - name: relay all mail through google mail (opensmtpd) 131 | blockinfile: 132 | dest: /etc/mail/smtpd.conf 133 | block: | 134 | table secrets db:/etc/mail/secrets.db 135 | action "relay" relay host tls+auth://googlemail@smtp.googlemail.com:587 auth 136 | match for any action "relay" 137 | 138 | - name: forward root mail to personal account 139 | lineinfile: dest='/etc/mail/aliases' regexp='^(#\ +)?root:' line='root{{\':\'}} {{email}}' 140 | 141 | - name: update aliases 142 | command: newaliases 143 | 144 | - name: set noatime and softdep on root partition 145 | lineinfile: dest='/etc/fstab' backrefs='yes' regexp='^([0-9a-f]{16}\.[a-z]{1})\ / ' line='\1 / ffs rw,noatime,softdep 1 1' 146 | 147 | - name: dns resolvers 148 | template: src='resolv.conf.j2' dest='/etc/resolv.conf' owner='root' group='wheel' mode='0644' 149 | 150 | - name: recursive caching dns server (unbound) 151 | template: 152 | src: 'unbound.conf.j2' 153 | dest: '/var/unbound/etc/unbound.conf' 154 | owner: 'root' 155 | group: 'wheel' 156 | mode: '0644' 157 | 158 | - name: copy unbound rc script 159 | copy: src='unbound' dest='/etc/rc.d/' owner='root' group='wheel' mode='a+x' 160 | 161 | - name: copy unbound conf 162 | copy: remote_src=true src=/var/unbound/etc/unbound.conf dest=/etc/unbound.conf 163 | 164 | - name: adservers mkdb script 165 | copy: src='adservers_mkdb.sh' dest='/usr/local/bin/adservers_mkdb' owner='root' group='wheel' mode='a+x' 166 | 167 | - name: create permitted adservers db 168 | template: src='adservers_permitted.db.j2' dest='/etc/adservers_permitted.db' owner='root' group='wheel' mode='0640' 169 | 170 | - name: create adservers db 171 | command: /usr/local/bin/adservers_mkdb 172 | ignore_errors: yes 173 | args: 174 | creates: /etc/adservers.db 175 | 176 | - name: cron to update adservers db 177 | cron: 178 | name: 'update adservers db' 179 | special_time: monthly 180 | job: '/usr/local/bin/adservers_mkdb' 181 | 182 | - name: network bridge 183 | copy: src='hostname.bridge0' dest='/etc/hostname.bridge0' owner='root' group='wheel' mode='0640' 184 | 185 | - name: virtual ethernet interface 186 | template: src='hostname.vether0.j2' dest='/etc/hostname.vether0' owner='root' group='wheel' mode='0640' 187 | 188 | - name: physical ethernet interfaces 189 | copy: src='hostname.emx' dest="/etc/hostname.{{item}}" owner="root" group="wheel" mode="0640" 190 | with_items: 191 | - 'em0' 192 | - 'em1' 193 | - 'em2' 194 | 195 | - name: ppp over ethernet 196 | template: src='hostname.pppoe0.j2' dest='/etc/hostname.pppoe0' owner='root' group='wheel' mode='0640' 197 | 198 | - name: wireless access point 199 | template: src='hostname.athn0.j2' dest='/etc/hostname.athn0' owner='root' group='wheel' mode='0640' 200 | 201 | - name: wireguard tunnel 202 | template: src='hostname.tun0.j2' dest='/etc/hostname.tun0' owner='root' group='wheel' mode='0640' 203 | 204 | - name: dhcpd 205 | template: src='dhcpd.conf.j2' dest='/etc/dhcpd.conf' owner='root' group='wheel' mode='0640' 206 | 207 | - name: sensorsd 208 | copy: src='sensorsd.conf' dest='/etc/sensorsd.conf' owner='root' group='wheel' mode='0600' 209 | 210 | - name: bogon mkdb script 211 | copy: src='bogons_mkdb.sh' dest='/usr/local/bin/bogons_mkdb' owner='root' group='wheel' mode='a+x' 212 | 213 | - name: create bogons db 214 | command: /usr/local/bin/bogons_mkdb 215 | args: 216 | creates: /etc/bogons.db 217 | 218 | - name: cron to update bogons db 219 | cron: 220 | name: 'update bogons db' 221 | special_time: weekly 222 | job: '/usr/local/bin/bogons_mkdb' 223 | 224 | - name: packet filter (pf) 225 | template: src='pf.conf.j2' dest='/etc/pf.conf' owner='root' group='wheel' mode='0600' validate='pfctl -nf %s' 226 | 227 | - name: syslog conf for pf 228 | lineinfile: dest='/etc/syslog.conf' line='local0.info /var/log/pflog.txt' 229 | 230 | - name: pf log rotator 231 | copy: src='pf_log_rotate.sh' dest='/usr/local/bin/pf_log_rotate' owner='root' group='wheel' mode='a+x' 232 | 233 | - name: cron to rotate pf logs 234 | cron: 235 | name: 'rotate packet filter logs' 236 | special_time: hourly 237 | job: '/usr/local/bin/pf_log_rotate' 238 | 239 | - name: pf log file 240 | file: path='/var/log/pflog.txt' owner='root' group='wheel' mode='0600' state='touch' 241 | 242 | - name: kernel configuration (sysctl) 243 | copy: src='sysctl.conf' dest='/etc/sysctl.conf' owner='root' group='wheel' mode='0600' 244 | 245 | - name: enable services 246 | lineinfile: dest='/etc/rc.conf.local' regexp='^{{item.name}}_flags=' line='{{item.name}}_flags=\"{{item.flags}}\"' create='yes' 247 | with_items: 248 | - { name: 'unbound', flags: '' } 249 | - { name: 'ntpd', flags: '-s' } 250 | - { name: 'dhcpd', flags: 'vether0' } 251 | - { name: 'sensorsd', flags: '' } 252 | - { name: 'sndiod', flags: 'NO' } 253 | - { name: 'nodeexporter', flags: '--web.listen-address={{network_address}}:9100' } 254 | - { name: 'wireguard', flags: 'tun0' } 255 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # homebrew-openbsd-pcengines-router 2 | 3 | ![server](./images/server.png "Server") 4 | 5 | - [About](#about) 6 | - [Purchases](#purchases) 7 | - [Assembly](#assembly) 8 | - [Running](#running) 9 | - [Memtest](#memtest) 10 | - [BIOS Update](#bios-update) 11 | - [OpenBSD](#openbsd) 12 | - [Immutability](#immutability) 13 | - [Ansible](#ansible) 14 | - [Noteworthy Configurations](#noteworth-configurations) 15 | - [Mail](#mail) 16 | - [Sensors](#sensors) 17 | - [Disk](#disk) 18 | - [DNS-over-TLS](#dns-over-tls) 19 | - [Networking](#networking) 20 | - [Network Time](#network-time) 21 | - [Firewalling](#firewalling) 22 | - [Bufferbloat](#bufferbloat) 23 | - [WireGuard](#wireguard) 24 | - [Prometheus](#prometheus) 25 | 26 | ## About 27 | 28 | This repo contains revised notes and Ansible collateral from building this fully open source router, circa early Feb 2017. 29 | 30 | 01/19 update: switch to DNS-over-TLS and a much larger adservers/bad sites [blacklist](https://v.firebog.net/hosts/lists.php) collated from multiple sources. 31 | 32 | 08/18 update: an official [WireGuard](https://www.wireguard.com/xplatform) userspace implementation is available and working on OpenBSD 6.3. The router now features this [revolutionary](#wireguard) VPN on tun0. 33 | 34 | 07/18 update: friends don't let friends have [Bufferbloat](https://en.wikipedia.org/wiki/Bufferbloat). [FQ-CoDel](https://en.wikipedia.org/wiki/CoDel) was merged into 6.2 so take [advantage](#bufferbloat) of it. 35 | 36 | 03/18 update: moved from OpenBSD 6.0-CURRENT to 6.3-STABLE. The router had a solid 400 day uptime. 37 | 38 | ![Router](./images/router.png "Router") 39 | 40 | #### What 41 | 42 | The router is built almost entirely of [PCEngines](http://www.pcengines.ch) components, running [coreboot](https://www.coreboot.org/) and [OpenBSD](https://www.openbsd.org), with all modifications to the base install immutable by way of [Ansible](https://www.ansible.com). 43 | 44 | #### Why 45 | 46 | A couple of reasons really. Firstly, it undoubtedly would have been more orthodox to put the FreeBSD-based pfSense on this kit and be done with it. However, while I've continued to follow OpenBSD's bi-annual releases with interest, it has been a good few years since I put it on a device for a reason. Plus, with the recent flurry of IoT/consumer router vulns, I get the feeling I'm going to want my home internet appliance protected by OpenBSD in the future. 47 | 48 | There was definitely a little nod to nostalgia as well. The project was reminiscent of building a homebrew router as a teenager for sharing out the family ISDN connection. Cobbling together an old Pentium box/NICs/Modem, unable to decide between the BSDs, Slackware, Gentoo, messing around with interface settings, `pf`, `ipchains`, `pppd` and so on. Busting out the null modem cable and `screen` also brought back fond memories from time spent interning in Sun Microsystem's labs. _Slapping OpenWRT or DD-WRT on a consumer router just wouldn't have given the same satisfaction_. 49 | 50 | Finally, pragmatically speaking, I wanted a little more muscle in my internet appliance for use cases like: OpenVPN, IRC bouncing, TOR, hosted git, torrenting daemon hooked up to my NAS etc., as opposed to delegating those use cases downstream to yet another device. The apu2c4 is the right mixture of power and efficiency (I read it draws ~6w but haven't checked). 51 | 52 | ## Purchases 53 | 54 | | Quantity | Part # | Description | USD | Origin | 55 | | -------- | ----------- | ------------------------------------- | -------- | ------ | 56 | | 1 | apu2c4 | APU.2C4 system board 4GB | \$106.00 | TW | 57 | | 1 | case1d2blku | Enclosure 3 LAN, black, USB | \$9.40 | CN | 58 | | 1 | ac12vuk | AC adapter 12V 2A UK for IT equipment | \$4.30 | CN | 59 | | 1 | msata16e | SSD M-Sata 16GB MLC Phison | \$14.50 | TW | 60 | | 1 | wle200nx | Compex WLE200NX miniPCI express card | \$17.50 | CN | 61 | | 2 | pigsma | Cable I-PEX -> reverse SMA | \$2.90 | TW | 62 | | 2 | antsmadb | Antenna reverse SMA dual band | \$4.00 | CN | 63 | | 1 | usbcom1a | Adapter USB to DB9F with cable | \$7.50 | CN | 64 | 65 | Total (incl. shipping to Australia): **\$183.20 USD** 66 | 67 | #### Additionally, you will need... 68 | 69 | - Small USB flash 70 | - Ethernet cables 71 | - OpenBSD 72 | - Screwdriver 73 | - Pliers 74 | - Patience 75 | 76 | ## Assembly 77 | 78 | ![Assemble](./images/assemble.png "Assemble") 79 | 80 | > NOTE: Prior to assembly it would be wise to read the APU2 series [board reference](http://www.pcengines.ch/pdf/apu2.pdf). 81 | 82 | 1. Install heat spreader and make sure it's pressed against the enclosure. 83 | 2. Attach bottom of enclosure, screw back in DB2 bolts and enclosure bolts. 84 | 85 | > NOTE: Don't forget to remove the DB9 bolts when slotting in the enclosure. 86 | 87 | 3. Attach the SSD (be sure to identify the mSATA from the mPCI slots) and any other mPCIs/SD cards. 88 | 4. Attach the wireless radio card. 89 | 90 | > NOTE: Wireless radio cards are ESD sensitive, especially the RF switch and the power amplifier. To avoid damage by electrostatic discharge, the following installation procedure is recommended from PCEngines: 91 | > 92 | > 1. Touch your hands and the bag containing the radio card to a ground point on the router board (for example one of the mounting holes). This will equalize the potential of radio card and router board. 93 | > 2. Install the radio card in the miniPCI express socket. 94 | > 3. Install the pigtail cable in the cut-out of the enclosure. This will ground the pigtail to the enclosure. 95 | > 4. Touch the I-PEX connector of the pigtail to the mounting hole (discharge), then plug onto the radio card (this is where the pre-requisite patience comes in. I found this very finicky and spent perhaps 15 mins just getting those bastards in!) 96 | 97 | 5. Attach the antennae, making sure the washer bolt is tight. 98 | 6. Finish screwing the enclosure and plug in the DB9 cable. 99 | 7. Plug the DC cable in first before power outlet to avoid arcing. 100 | 101 | ## Running 102 | 103 | #### Serial Console 104 | 105 | I was using a Macbook/macOS for this project. 106 | 107 | Serial Settings: 115200 baud rate, 8N1 (8 data bits, no parity, 1 stop bit). See [here](http://pcengines.ch/howto.htm#serialconsole). 108 | 109 | The [PCEngines usbcom1a](http://www.pcengines.ch/usbcom1a.htm) uses the Silicon Labs CP2104 controller for which you can grab the driver [here](https://www.silabs.com/products/mcu/pages/usbtouartbridgevcpdrivers.aspx) (Mac/Linux/Windows) 110 | 111 | > 2018 update: to get this driver working on Sierra I had to install the legacy version of v5 _twice_. The double install gets the devices created and kernel driver loaded. No response from SiLabs on a fix at the time of writing this. 112 | 113 | ```bash 114 | # Use a terminal multiplexer like `screen` 115 | screen /dev/tty.SLAB_USBtoUART 115200 116 | # Remember you're in screen, usual rules apply. Quit or Detach (below): 117 | 118 | ``` 119 | 120 | ![Screen Session](./images/serial_console.png "Screen Session") 121 | 122 | > NOTE: pay attention to the BIOS version here, later you will decide whether it needs updated or not. 123 | 124 | ## Memtest 125 | 126 | First and foremost, you should probably run a Memtest. 127 | 128 | 1. Power cycle the router. 129 | 2. Quickly fire in an F10 (remember fn key if Macbook) before the boot sequence gets too far. 130 | 131 | > NOTE: This is a good time to make sure your SATA/PCI slots are seated and registered. They show up in the F10 menu. 132 | 133 | 3. Select: Payload [memtest] 134 | 135 | ![Memtest](./images/memtest.png "Memtest") 136 | 137 | 4. ~2 hours later I had completed one pass which was good enough for me. 138 | 139 | > NOTE: The apu2c4's AMD GX-412TC has a max temp rating of 90C. Mine hit a max of 69C during this test in a 30C ambient temperature (Australian summer) 140 | 141 | ## BIOS Update 142 | 143 | If the BIOS version you have (as noted above) is earlier than that which is available on [PCEngines](http://pcengines.ch/howto.htm#bios), continue with this section. 144 | 145 | > NOTE: In hindsight I realised I could have flashed the ROM from within OpenBSD using [`flashrom`](http://ports.su/sysutils/flashrom). You may alernatively wish to just go ahead with the BSD install and update your BIOS afterwards. Regardless, what follows are the notes I took, some of which will be relevant anyway. 146 | 147 | #### Prepare 148 | 149 | This step involves creating a bootable USB containing [PCEngine's TinyCoreLinux](http://pcengines.ch/howto.htm#TinyCoreLinux) and the latest [APU2 ROM](http://pcengines.ch/howto.htm#bios). At the time of writing this, the following versions and instructions applied to macOS: 150 | 151 | | File | Link | Digest | 152 | | ------------------------ | ------------------------------------------------ | -------------------------------- | 153 | | PCEngines TinyCore Linux | http://pcengines.ch/file/apu2-tinycore6.4.img.gz | 48b8e0f21792648889aa99bf8156fed7 | 154 | | PCEngines apu2 ROM | http://www.pcengines.ch/file/apu2_160311.zip | 780a8ffaa034e013fef7126f3f986646 | 155 | 156 | 1. Grab and verify the distributions: 157 | 158 | ```bash 159 | # Download 160 | curl -O http://pcengines.ch/file/apu2-tinycore6.4.img.gz 161 | curl -O http://www.pcengines.ch/file/apu2_160311.zip 162 | 163 | # Uncompress 164 | gunzip apu2-tinycore6.4.img.gz 165 | unzip apu2_160311.zip 166 | 167 | # Verify 168 | md5 apu2-tinycore6.4.img # 48b8e0f21792648889aa99bf8156fed7 169 | md5 apu2_160311.rom # 780a8ffaa034e013fef7126f3f986646 170 | ``` 171 | 172 | 2. Add the ROM to the disk image. The easiest way I could find was simply to mount the IMG and drag the ROM into it, both using Finder. 173 | 3. Unmount both the IMG file and the USB 174 | 4. Write the IMG to the _raw_ USB device. In my case this was `disk2`. Double check with `diskutil list`. 175 | ```bash 176 | sudo dd if=apu2-tinycore6.4.img of=/dev/rdisk2 bs=1m 177 | ``` 178 | 179 | #### Flash the BIOS 180 | 181 | 1. With the USB in a slot, power cycle the router. 182 | 2. Either fire in an F10 again, or wait and the boot order should kick in and launch TinyCore Linux from the USB. 183 | 3. This should land you in a rootshell with `/media/SYSLINUX` the mounted USB. Proceed to flash the ROM: 184 | ```bash 185 | flashrom -p internal -w /media/SYSLINUX/apu2_160311.rom 186 | ``` 187 | 4. Reboot after you see the final `Verifying flash... VERIFIED`. 188 | 189 | ## OpenBSD 190 | 191 | If one of your hopes for this kit is for it to function as a wireless access point, and you've bought the `wlen200nx` above (Atheros AR9280 chipset), or any Atheros based radio card, then you should look into the current status of 11n hostap support for [`athn(4)`](http://man.openbsd.org/athn). 192 | 193 | At this time 6.0 is the latest `OpenBSD-STABLE` and [`athn(4)`](http://man.openbsd.org/athn) supports only `a/b/g` modes for hostap. I tested `b/g` on `-STABLE`, unscientifically at first through web browsing and `speedtest-cli`, then a little more scientifically with a few runs through `iperf`. 194 | 195 | > NOTE: you'll need the [dual-band antenna](https://www.pcengines.ch/antsmadb.htm) if testing `a`. 196 | 197 | The results were consistently disappointing - unusable bulk data transfer rates regardless of mode and channel. YMMV here, but others seem have had similar results according to the OpenBSD mailing lists. It seems the WiFi stack is still basic in this particular area, and I read there's simply not enough devs working on it. 198 | 199 | However! `11n` support is being [actively worked on](https://marc.info/?l=openbsd-tech&m=148396652007923&w=2) in 6.0 `-CURRENT`. I am running this version and seeing significant improvements in data transfer speeds. Unfortunately still not as fast as my old Linksys/DD-WRT based access point. Though I still need to spend more time looking at optimising the OpenBSD network stack and selecting the perfect channel for my apartment, plus I can always fall back on using the Linksys unit as a bridged access point if all else fails. 200 | 201 | Anyway, choose `-STABLE` or `-CURRENT` (or perhaps 6.1+) based on your WiFi access point needs and continue. 202 | 203 | #### Install 204 | 205 | 1. You will want to grab the latest OpenBSD filesystem image and verify its SHA256 hash: 206 | 207 | > NOTE: Use a [mirror](https://www.openbsd.org/ftp.html) local to you. 208 | 209 | ```bash 210 | # STABLE 211 | curl -O http://ftp.openbsd.org/pub/OpenBSD/6.0/amd64/install60.fs 212 | shasum -a 256 install60.fs # Matches the install60.fs line item @ https://ftp.openbsd.org/pub/OpenBSD/6.0/amd64/SHA256 213 | 214 | # CURRENT 215 | curl -O https://ftp.openbsd.org/pub/OpenBSD/snapshots/amd64/install60.fs 216 | shasum -a 256 install60.fs # Matches the install60.fs line item @ https://ftp.openbsd.org/pub/OpenBSD/snapshots/amd64/SHA256 217 | ``` 218 | 219 | 2. Stick the flash drive back into the Macbook, unmount it and write the OpenBSD filesystem to the drive: 220 | 221 | ```bash 222 | # Assuming `disk2` as before (again, confirm with `diskutil list`) 223 | diskutil unmountDisk /dev/disk2 224 | 225 | # Write install60.fs to the flash drive 226 | sudo dd if=install60.fs of=/dev/rdisk2 bs=1m 227 | 228 | # macOS mounted it again for me after this, so... 229 | diskutil unmountDisk /dev/disk2 230 | ``` 231 | 232 | 3. Plug the flash drive into the router and turn it on. Assuming the boot sequence is the same as mine this will land you at the `boot>` prompt. If not, launch the USB from the BIOS menu or edit the BIOS settings and change the order so that USB slots are first up. 233 | 4. Pay attention to the serial console [FAQ](https://www.openbsd.org/faq/faq7.html). I found I had to tell the boot process to use the serial port as a console and change the baud rate. Though I found I didn't have to persist this in [boot.conf(5)](http://man.openbsd.org/amd64/boot.conf) as the FAQ said; it seemed to get done for me. 234 | 235 | ```bash 236 | boot> set tty com0 237 | boot> stty com0 115200 238 | ``` 239 | 240 | 5. Go ahead with an OpenBSD install. 241 | 242 | ```bash 243 | Welcome to the OpenBSD/amd64 6.0 installation program. 244 | (I)nstall, (U)pgrade, (A)utoinstall or (S)hell? 245 | ``` 246 | 247 | > NOTE: I won't detail install steps here; the [documentation](https://www.openbsd.org/faq/faq4.html#Install) has a sterling reputation for a reason. 248 | > 249 | > That said, you'll not have much need for the X11 and game install sets so you may as well de-select those. 250 | 251 | ## Immutability 252 | 253 | It would be poor form to not [practice what I preach](https://martinfowler.com/bliki/ImmutableServer.html) in my day job and so I've taken to driving all modifications to the userspace using automation. The "immutability" aspect could be further improved with something like [Packer](https://www.packer.io) building out the OpenBSD filesystem image but that seemed like overkill. I just wanted to be able to blow away the OS and bring it back to where it was. I took the easy way out with (some pretty unidiomatic) Ansible and may revisit with something like NixOps which has been on my learn-list for ages. 254 | 255 | ## Ansible 256 | 257 | Nothing special or highly refined in terms of Ansible here. 258 | 259 | The `settings.yml` contains all variables used in the jinja2 templates and `./go.sh ` orchestrates a few Ansible runs against the new OpenBSD install. 260 | 261 | Firstly to bootstrap Python for Ansible (using password auth via paramiko): 262 | 263 | ```bash 264 | ansible -m raw -c paramiko -u "${USER}" -k \ 265 | -b --become-method=su --ask-su-pass -a \ 266 | "PKG_PATH=https://ftp.openbsd.org/pub/OpenBSD/snapshots/packages/amd64 \ 267 | pkg_add python-${PYTHON_VERSION}" -i "$1," "$1" 268 | ``` 269 | 270 | Then a 1st provisioning pass enables [`doas(1)`](http://man.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/doas.1) and disables password auth in lieu of successfully configuring public keys: 271 | 272 | ```bash 273 | ansible-playbook bootstrap.yml -kKi "$1," 274 | ``` 275 | 276 | The main event provisions the rest of the configuration using key based logins and [`doas(1)`](http://man.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/doas.1) for superuser access: 277 | 278 | ```bash 279 | ansible-playbook provision.yml -i "$1," 280 | ``` 281 | 282 | ## Noteworthy Configurations 283 | 284 | #### Mail 285 | 286 | OpenSMTPD is configured to externally relay using Google's SMTP servers. To do the same, you will need to enable ["less secure apps"](https://www.google.com/settings/security/lesssecureapps) on the Gmail account. Obviously use a burner account exclusively for this purpose rather than your personal Google account. 287 | 288 | With SMTP relay set up, the last steps involving mail are to configure a mail alias for `root` to be the personal account in `settings.yml`. 289 | 290 | #### Sensors 291 | 292 | [sensorsd(8)](http://man.openbsd.org/OpenBSD-current/man8/sensorsd.8) is enabled to monitor the CPU temperature. I chose the arbitrary value of 70C. If the CPU temp exceeds this value then a mail will be sent to root (and thus my private email account as above). 293 | 294 | #### Disk 295 | 296 | [fstab(5)](http://man.openbsd.org/fstab) is updated to mount the root filesystem with no access time logging and soft updates for performance reasons. 297 | 298 | #### DNS-over-TLS 299 | 300 | Unbound DNS is enabled and configured to be a recursive caching DNS with upstream DNS-over-TLS nameservers (so my DNS requests are encrypted in transit). At the other end I'm using [Cloudflare](https://blog.cloudflare.com/announcing-1111). From where I am, Cloudflare is faster than Google, and state they'll never use the data and wipe logs within 24h, with KPMG on retainer performing audits keeping them to their word. 301 | 302 | Fixed LAN clients declared in `settings.yml` are added as local-data resolutions, including the server hostname itself. The are also set up for reverse DNS. 303 | 304 | Finally a [blacklist of domains](https://v.firebog.net/hosts/lists.php) is scraped, parsed and loaded, with each configured to resolve localhost i.e. null routed. The DB is updated monthly from upstream using a cron. 305 | 306 | #### Network Time 307 | 308 | NTP is enabled and pointed at a pool as per `settings.yml` and [`dhcpd(8)`](http://man.openbsd.org/dhcpd.8) is configured to advertise this to clients. 309 | 310 | #### Networking 311 | 312 | The `apu2c4` board has 3 NICs. The first of which, the `em0` interface, is used in conjunction with a `pppoe0` interface to make a connection to my ISP. Username/password configured in `settings.yml`. 313 | 314 | The remaining NIC interfaces (`em1` and `em2`) are plumbed along with `athn0`, the wireless card, into a link aggregate interface which binds on the configured ip/netmask/broadcast in `settings.yml` (as the `vether0` interface). This completes the egress and LAN networking. 315 | 316 | #### Firewalling 317 | 318 | The [pf.conf(5)](http://man.openbsd.org/pf.conf.5) jinja2 template is individually annotated with comments. Below are a few highlights: 319 | 320 | - A table of bogus private addresses ([bogons](https://en.wikipedia.org/wiki/Bogon_filtering)) is used in rules on the egress interface (in `pf` speak, this is the current default route). Anything coming in on `egress` destined for, or out on `egress` destined to one of these addresses will be dropped `quick`. 321 | - This table is updated weekly using a cron, and reloaded into pf via [`pfctl(8)`](http://man.openbsd.org/pfctl.8). The bogon list is grabbed from [Team Cymru's reference](https://www.team-cymru.org/bogon-reference.html). 322 | - Scrubbing/normalization is performed on all incoming packets and MSS clamping (1452, matching a respective kernel param in `sysctl.conf`) is performed on `egress`. 323 | - Interactive SSH sessions, ICMP, DNS queries and packets with low-delay ToS are given priority. 324 | - SSH connections are allowed in to the external port declared in the `settings.yml` (Ansible also updates `sshd_config` based on this port). 325 | 326 | > NOTE: Setting the external port to :443 and using something like [`corkscrew`](http://agroman.net/corkscrew) would allow for tunneling out of a corporate network via the https proxy, if one were so inclined. 327 | 328 | - A state table is maintained for suspected SSH brute force attempts. Any client matching a brute force pattern will be added to this table and subsequently dropped. 329 | - [`syslogd(8)`](http://man.openbsd.org/syslogd) is used to translate firewall logs into ASCII format, and a cron and [`pf_log_rotate.sh`](./files/pf_log_rotate.sh) is used for rotation. 330 | 331 | #### Bufferbloat 332 | 333 | Also found in the [pf.conf(5)](http://man.openbsd.org/pf.conf.5) is a fix for [Bufferbloat](https://www.bufferbloat.net/projects/bloat/wiki/Introduction/). The `uplink_` variables should be set to about [90-95%](https://www.reddit.com/r/openbsd/comments/75ps6h/fqcodel_and_pf/doca4uv) of your uplink up/down in megabytes. 334 | 335 | Use DSLreports.com's SpeedTest for Bufferbloat analysis: 336 | 337 | ![Bufferbloat](./images/bufferbloat.png "Bufferbloat") 338 | 339 | #### WireGuard 340 | 341 | The [WireGuard](https://www.wireguard.com)-go implementation is installed and configured via the provisioning process. It's possible to change things like port, CIDR blocks, peers etc. through `settings.yml`. 342 | 343 | I'm mostly remotely accessing my home network with macOS. I'd recommend making use of [pass](https://www.passwordstore.org/) by the same author as WireGuard for encrypting private keys. This is achieved simply by replacing the `PrivateKey` line under `[Interface]` in the WireGuard config with: 344 | 345 | ```bash 346 | PostUp = wg set %i private-key <(su user -c "export PASSWORD_STORE_DIR=/path/to/pass/store/; pass WireGuard/private-keys/%i") 347 | ``` 348 | 349 | In terms of workflow on macOS, [here's](https://techcrunch.com/2018/07/28/how-i-made-my-own-wireguard-vpn-server/) how you'd use AppleScript and the WireGuard CLI tools. 350 | 351 | #### Prometheus 352 | 353 | Prometheus' `node_exporter` is installed and configured as a system daemon as part of the Ansible provision run. 354 | 355 | I have Prometheus itself running, along with Alertmanager and Grafana, on another server within my network. This is the [dashboard](./OpenBSDNodes.json) I'm using. It looks something like this: 356 | 357 | ![Grafana 1](./images/grafana1.png "Grafana 1") 358 | 359 | ![Grafana 2](./images/grafana2.png "Grafana 2") 360 | 361 | #### TODO 362 | 363 | - IRC bouncer 364 | - Port knocking for LAN services 365 | - Set certain partitions as read-only 366 | -------------------------------------------------------------------------------- /OpenBSDNodes.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "Prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "grafana", 15 | "id": "grafana", 16 | "name": "Grafana", 17 | "version": "5.0.4" 18 | }, 19 | { 20 | "type": "panel", 21 | "id": "graph", 22 | "name": "Graph", 23 | "version": "5.0.0" 24 | }, 25 | { 26 | "type": "datasource", 27 | "id": "prometheus", 28 | "name": "Prometheus", 29 | "version": "5.0.0" 30 | }, 31 | { 32 | "type": "panel", 33 | "id": "singlestat", 34 | "name": "Singlestat", 35 | "version": "5.0.0" 36 | } 37 | ], 38 | "annotations": { 39 | "list": [ 40 | { 41 | "builtIn": 1, 42 | "datasource": "-- Grafana --", 43 | "enable": true, 44 | "hide": true, 45 | "iconColor": "rgba(0, 211, 255, 1)", 46 | "name": "Annotations & Alerts", 47 | "type": "dashboard" 48 | } 49 | ] 50 | }, 51 | "description": "FreeBSD node_exporter graphs", 52 | "editable": true, 53 | "gnetId": 4260, 54 | "graphTooltip": 0, 55 | "id": null, 56 | "iteration": 1523959828613, 57 | "links": [], 58 | "panels": [ 59 | { 60 | "collapsed": false, 61 | "gridPos": { 62 | "h": 1, 63 | "w": 24, 64 | "x": 0, 65 | "y": 0 66 | }, 67 | "id": 247, 68 | "panels": [], 69 | "title": "Node Overview", 70 | "type": "row" 71 | }, 72 | { 73 | "cacheTimeout": null, 74 | "colorBackground": false, 75 | "colorValue": false, 76 | "colors": [ 77 | "rgba(245, 54, 54, 0.9)", 78 | "rgba(237, 129, 40, 0.89)", 79 | "rgba(50, 172, 45, 0.97)" 80 | ], 81 | "datasource": "${DS_PROMETHEUS}", 82 | "decimals": 2, 83 | "description": "Total RAM", 84 | "format": "bytes", 85 | "gauge": { 86 | "maxValue": 100, 87 | "minValue": 0, 88 | "show": false, 89 | "thresholdLabels": false, 90 | "thresholdMarkers": true 91 | }, 92 | "gridPos": { 93 | "h": 2, 94 | "w": 4, 95 | "x": 0, 96 | "y": 1 97 | }, 98 | "id": 75, 99 | "interval": null, 100 | "links": [], 101 | "mappingType": 1, 102 | "mappingTypes": [ 103 | { 104 | "name": "value to text", 105 | "value": 1 106 | }, 107 | { 108 | "name": "range to text", 109 | "value": 2 110 | } 111 | ], 112 | "maxDataPoints": 100, 113 | "minSpan": 4, 114 | "nullPointMode": "null", 115 | "nullText": null, 116 | "postfix": "", 117 | "postfixFontSize": "70%", 118 | "prefix": "", 119 | "prefixFontSize": "50%", 120 | "rangeMaps": [ 121 | { 122 | "from": "null", 123 | "text": "N/A", 124 | "to": "null" 125 | } 126 | ], 127 | "sparkline": { 128 | "fillColor": "rgba(31, 118, 189, 0.18)", 129 | "full": false, 130 | "lineColor": "rgb(31, 120, 193)", 131 | "show": false 132 | }, 133 | "tableColumn": "", 134 | "targets": [ 135 | { 136 | "expr": "node_memory_size_bytes{instance=~\"$node:$port\"}", 137 | "intervalFactor": 1, 138 | "refId": "A", 139 | "step": 900 140 | } 141 | ], 142 | "thresholds": "", 143 | "title": "Total RAM", 144 | "type": "singlestat", 145 | "valueFontSize": "50%", 146 | "valueMaps": [ 147 | { 148 | "op": "=", 149 | "text": "N/A", 150 | "value": "null" 151 | } 152 | ], 153 | "valueName": "current" 154 | }, 155 | { 156 | "cacheTimeout": null, 157 | "colorBackground": false, 158 | "colorValue": false, 159 | "colors": [ 160 | "rgba(245, 54, 54, 0.9)", 161 | "rgba(237, 129, 40, 0.89)", 162 | "rgba(50, 172, 45, 0.97)" 163 | ], 164 | "datasource": "${DS_PROMETHEUS}", 165 | "decimals": 2, 166 | "description": "Total SWAP", 167 | "format": "bytes", 168 | "gauge": { 169 | "maxValue": 100, 170 | "minValue": 0, 171 | "show": false, 172 | "thresholdLabels": false, 173 | "thresholdMarkers": true 174 | }, 175 | "gridPos": { 176 | "h": 2, 177 | "w": 4, 178 | "x": 4, 179 | "y": 1 180 | }, 181 | "id": 18, 182 | "interval": null, 183 | "links": [], 184 | "mappingType": 1, 185 | "mappingTypes": [ 186 | { 187 | "name": "value to text", 188 | "value": 1 189 | }, 190 | { 191 | "name": "range to text", 192 | "value": 2 193 | } 194 | ], 195 | "maxDataPoints": 100, 196 | "minSpan": 4, 197 | "nullPointMode": "null", 198 | "nullText": null, 199 | "postfix": "", 200 | "postfixFontSize": "70%", 201 | "prefix": "", 202 | "prefixFontSize": "50%", 203 | "rangeMaps": [ 204 | { 205 | "from": "null", 206 | "text": "N/A", 207 | "to": "null" 208 | } 209 | ], 210 | "sparkline": { 211 | "fillColor": "rgba(31, 118, 189, 0.18)", 212 | "full": false, 213 | "lineColor": "rgb(31, 120, 193)", 214 | "show": false 215 | }, 216 | "tableColumn": "", 217 | "targets": [ 218 | { 219 | "expr": "node_memory_swap_size_bytes{instance=~\"$node:$port\"}", 220 | "format": "time_series", 221 | "intervalFactor": 1, 222 | "refId": "A", 223 | "step": 900 224 | } 225 | ], 226 | "thresholds": "", 227 | "title": "Total Swap", 228 | "type": "singlestat", 229 | "valueFontSize": "50%", 230 | "valueMaps": [ 231 | { 232 | "op": "=", 233 | "text": "N/A", 234 | "value": "null" 235 | } 236 | ], 237 | "valueName": "current" 238 | }, 239 | { 240 | "cacheTimeout": null, 241 | "colorBackground": false, 242 | "colorValue": false, 243 | "colors": [ 244 | "rgba(50, 172, 45, 0.97)", 245 | "rgba(237, 129, 40, 0.89)", 246 | "rgba(245, 54, 54, 0.9)" 247 | ], 248 | "datasource": "${DS_PROMETHEUS}", 249 | "decimals": null, 250 | "description": "Total RootFS", 251 | "format": "bytes", 252 | "gauge": { 253 | "maxValue": 100, 254 | "minValue": 0, 255 | "show": false, 256 | "thresholdLabels": false, 257 | "thresholdMarkers": true 258 | }, 259 | "gridPos": { 260 | "h": 2, 261 | "w": 4, 262 | "x": 8, 263 | "y": 1 264 | }, 265 | "id": 23, 266 | "interval": null, 267 | "links": [], 268 | "mappingType": 1, 269 | "mappingTypes": [ 270 | { 271 | "name": "value to text", 272 | "value": 1 273 | }, 274 | { 275 | "name": "range to text", 276 | "value": 2 277 | } 278 | ], 279 | "maxDataPoints": 100, 280 | "minSpan": 4, 281 | "nullPointMode": "null", 282 | "nullText": null, 283 | "postfix": "", 284 | "postfixFontSize": "50%", 285 | "prefix": "", 286 | "prefixFontSize": "50%", 287 | "rangeMaps": [ 288 | { 289 | "from": "null", 290 | "text": "N/A", 291 | "to": "null" 292 | } 293 | ], 294 | "sparkline": { 295 | "fillColor": "rgba(31, 118, 189, 0.18)", 296 | "full": false, 297 | "lineColor": "rgb(31, 120, 193)", 298 | "show": false 299 | }, 300 | "tableColumn": "", 301 | "targets": [ 302 | { 303 | "expr": "node_filesystem_size_bytes{instance=~\"$node:$port\",mountpoint=\"/\"}", 304 | "format": "time_series", 305 | "hide": false, 306 | "intervalFactor": 1, 307 | "refId": "A", 308 | "step": 900 309 | } 310 | ], 311 | "thresholds": "70,90", 312 | "title": "Total RootFS", 313 | "type": "singlestat", 314 | "valueFontSize": "50%", 315 | "valueMaps": [ 316 | { 317 | "op": "=", 318 | "text": "N/A", 319 | "value": "null" 320 | } 321 | ], 322 | "valueName": "current" 323 | }, 324 | { 325 | "aliasColors": {}, 326 | "bars": false, 327 | "dashLength": 10, 328 | "dashes": false, 329 | "datasource": "${DS_PROMETHEUS}", 330 | "description": "", 331 | "fill": 2, 332 | "gridPos": { 333 | "h": 10, 334 | "w": 12, 335 | "x": 12, 336 | "y": 1 337 | }, 338 | "id": 40, 339 | "legend": { 340 | "alignAsTable": true, 341 | "avg": true, 342 | "current": true, 343 | "max": true, 344 | "min": true, 345 | "rightSide": false, 346 | "show": true, 347 | "total": false, 348 | "values": true 349 | }, 350 | "lines": true, 351 | "linewidth": 1, 352 | "links": [], 353 | "nullPointMode": "null", 354 | "percentage": false, 355 | "pointradius": 5, 356 | "points": false, 357 | "renderer": "flot", 358 | "seriesOverrides": [], 359 | "spaceLength": 10, 360 | "stack": true, 361 | "steppedLine": false, 362 | "targets": [ 363 | { 364 | "expr": "irate(node_scrape_collector_duration_seconds{instance=~\"$node:$port\"}[5m])", 365 | "format": "time_series", 366 | "hide": false, 367 | "intervalFactor": 2, 368 | "legendFormat": "{{collector}} - Scrape duration", 369 | "refId": "A", 370 | "step": 30 371 | } 372 | ], 373 | "thresholds": [], 374 | "timeFrom": null, 375 | "timeShift": null, 376 | "title": "Scrape Durations", 377 | "tooltip": { 378 | "shared": true, 379 | "sort": 0, 380 | "value_type": "individual" 381 | }, 382 | "type": "graph", 383 | "xaxis": { 384 | "buckets": null, 385 | "mode": "time", 386 | "name": null, 387 | "show": true, 388 | "values": [] 389 | }, 390 | "yaxes": [ 391 | { 392 | "format": "s", 393 | "label": "Seconds", 394 | "logBase": 1, 395 | "max": null, 396 | "min": null, 397 | "show": true 398 | }, 399 | { 400 | "format": "short", 401 | "label": null, 402 | "logBase": 1, 403 | "max": null, 404 | "min": null, 405 | "show": false 406 | } 407 | ] 408 | }, 409 | { 410 | "cacheTimeout": null, 411 | "colorBackground": false, 412 | "colorValue": true, 413 | "colors": [ 414 | "rgba(50, 172, 45, 0.97)", 415 | "rgba(237, 129, 40, 0.89)", 416 | "rgba(245, 54, 54, 0.9)" 417 | ], 418 | "datasource": "${DS_PROMETHEUS}", 419 | "decimals": 0, 420 | "description": "Non available RAM memory", 421 | "format": "percent", 422 | "gauge": { 423 | "maxValue": 100, 424 | "minValue": 0, 425 | "show": true, 426 | "thresholdLabels": false, 427 | "thresholdMarkers": true 428 | }, 429 | "gridPos": { 430 | "h": 4, 431 | "w": 4, 432 | "x": 0, 433 | "y": 3 434 | }, 435 | "hideTimeOverride": false, 436 | "id": 16, 437 | "interval": null, 438 | "links": [], 439 | "mappingType": 1, 440 | "mappingTypes": [ 441 | { 442 | "name": "value to text", 443 | "value": 1 444 | }, 445 | { 446 | "name": "range to text", 447 | "value": 2 448 | } 449 | ], 450 | "maxDataPoints": 100, 451 | "minSpan": 4, 452 | "nullPointMode": "null", 453 | "nullText": null, 454 | "postfix": "", 455 | "postfixFontSize": "50%", 456 | "prefix": "", 457 | "prefixFontSize": "50%", 458 | "rangeMaps": [ 459 | { 460 | "from": "null", 461 | "text": "N/A", 462 | "to": "null" 463 | } 464 | ], 465 | "sparkline": { 466 | "fillColor": "rgba(31, 118, 189, 0.18)", 467 | "full": false, 468 | "lineColor": "rgb(31, 120, 193)", 469 | "show": true 470 | }, 471 | "tableColumn": "", 472 | "targets": [ 473 | { 474 | "expr": "(node_memory_active_bytes{instance=~\"$node:$port\"} * 100) / node_memory_size_bytes{instance=~\"$node:$port\"}", 475 | "format": "time_series", 476 | "hide": false, 477 | "intervalFactor": 1, 478 | "refId": "B", 479 | "step": 900 480 | } 481 | ], 482 | "thresholds": "80,90", 483 | "title": "Used RAM Memory", 484 | "type": "singlestat", 485 | "valueFontSize": "80%", 486 | "valueMaps": [], 487 | "valueName": "current" 488 | }, 489 | { 490 | "cacheTimeout": null, 491 | "colorBackground": false, 492 | "colorValue": true, 493 | "colors": [ 494 | "rgba(50, 172, 45, 0.97)", 495 | "rgba(237, 129, 40, 0.89)", 496 | "rgba(245, 54, 54, 0.9)" 497 | ], 498 | "datasource": "${DS_PROMETHEUS}", 499 | "decimals": null, 500 | "description": "Waiting to: https://github.com/derekmarcotte/node_exporter/blob/3bcdd14906307da57641ea8321d47909b90d98fa/collector/memory_bsd.go", 501 | "format": "percent", 502 | "gauge": { 503 | "maxValue": 100, 504 | "minValue": 0, 505 | "show": true, 506 | "thresholdLabels": false, 507 | "thresholdMarkers": true 508 | }, 509 | "gridPos": { 510 | "h": 4, 511 | "w": 4, 512 | "x": 4, 513 | "y": 3 514 | }, 515 | "id": 21, 516 | "interval": null, 517 | "links": [], 518 | "mappingType": 1, 519 | "mappingTypes": [ 520 | { 521 | "name": "value to text", 522 | "value": 1 523 | }, 524 | { 525 | "name": "range to text", 526 | "value": 2 527 | } 528 | ], 529 | "maxDataPoints": 100, 530 | "minSpan": 4, 531 | "nullPointMode": "null", 532 | "nullText": null, 533 | "postfix": "", 534 | "postfixFontSize": "50%", 535 | "prefix": "", 536 | "prefixFontSize": "50%", 537 | "rangeMaps": [ 538 | { 539 | "from": "null", 540 | "text": "N/A", 541 | "to": "null" 542 | } 543 | ], 544 | "sparkline": { 545 | "fillColor": "rgba(31, 118, 189, 0.18)", 546 | "full": false, 547 | "lineColor": "rgb(31, 120, 193)", 548 | "show": true 549 | }, 550 | "tableColumn": "", 551 | "targets": [ 552 | { 553 | "expr": "(node_memory_swap_used_bytes{instance=~\"$node:$port\"} * 100) / node_memory_swap_size_bytes{instance=~\"$node:$port\"}", 554 | "format": "time_series", 555 | "intervalFactor": 1, 556 | "refId": "A", 557 | "step": 900 558 | } 559 | ], 560 | "thresholds": "10,25", 561 | "title": "Used SWAP", 562 | "type": "singlestat", 563 | "valueFontSize": "80%", 564 | "valueMaps": [ 565 | { 566 | "op": "=", 567 | "text": "N/A", 568 | "value": "null" 569 | } 570 | ], 571 | "valueName": "current" 572 | }, 573 | { 574 | "cacheTimeout": null, 575 | "colorBackground": false, 576 | "colorValue": true, 577 | "colors": [ 578 | "rgba(50, 172, 45, 0.97)", 579 | "rgba(237, 129, 40, 0.89)", 580 | "rgba(245, 54, 54, 0.9)" 581 | ], 582 | "datasource": "${DS_PROMETHEUS}", 583 | "decimals": null, 584 | "description": "Used Root FS", 585 | "format": "percent", 586 | "gauge": { 587 | "maxValue": 100, 588 | "minValue": 0, 589 | "show": true, 590 | "thresholdLabels": false, 591 | "thresholdMarkers": true 592 | }, 593 | "gridPos": { 594 | "h": 4, 595 | "w": 4, 596 | "x": 8, 597 | "y": 3 598 | }, 599 | "id": 154, 600 | "interval": null, 601 | "links": [], 602 | "mappingType": 1, 603 | "mappingTypes": [ 604 | { 605 | "name": "value to text", 606 | "value": 1 607 | }, 608 | { 609 | "name": "range to text", 610 | "value": 2 611 | } 612 | ], 613 | "maxDataPoints": 100, 614 | "minSpan": 4, 615 | "nullPointMode": "null", 616 | "nullText": null, 617 | "postfix": "", 618 | "postfixFontSize": "50%", 619 | "prefix": "", 620 | "prefixFontSize": "50%", 621 | "rangeMaps": [ 622 | { 623 | "from": "null", 624 | "text": "N/A", 625 | "to": "null" 626 | } 627 | ], 628 | "sparkline": { 629 | "fillColor": "rgba(31, 118, 189, 0.18)", 630 | "full": false, 631 | "lineColor": "rgb(31, 120, 193)", 632 | "show": true 633 | }, 634 | "tableColumn": "", 635 | "targets": [ 636 | { 637 | "expr": "100 - ((node_filesystem_avail_bytes{instance=~\"$node:$port\",mountpoint=\"/\"} * 100) / node_filesystem_size_bytes{instance=~\"$node:$port\",mountpoint=\"/\"})", 638 | "format": "time_series", 639 | "intervalFactor": 1, 640 | "refId": "A", 641 | "step": 900 642 | } 643 | ], 644 | "thresholds": "80,90", 645 | "title": "Used Root FS", 646 | "type": "singlestat", 647 | "valueFontSize": "80%", 648 | "valueMaps": [ 649 | { 650 | "op": "=", 651 | "text": "N/A", 652 | "value": "null" 653 | } 654 | ], 655 | "valueName": "current" 656 | }, 657 | { 658 | "cacheTimeout": null, 659 | "colorBackground": false, 660 | "colorValue": true, 661 | "colors": [ 662 | "rgba(50, 172, 45, 0.97)", 663 | "rgba(237, 129, 40, 0.89)", 664 | "rgba(245, 54, 54, 0.9)" 665 | ], 666 | "datasource": "${DS_PROMETHEUS}", 667 | "decimals": null, 668 | "description": "Busy state of all CPU cores together", 669 | "format": "percent", 670 | "gauge": { 671 | "maxValue": 100, 672 | "minValue": 0, 673 | "show": true, 674 | "thresholdLabels": false, 675 | "thresholdMarkers": true 676 | }, 677 | "gridPos": { 678 | "h": 4, 679 | "w": 4, 680 | "x": 0, 681 | "y": 7 682 | }, 683 | "id": 20, 684 | "interval": null, 685 | "links": [], 686 | "mappingType": 1, 687 | "mappingTypes": [ 688 | { 689 | "name": "value to text", 690 | "value": 1 691 | }, 692 | { 693 | "name": "range to text", 694 | "value": 2 695 | } 696 | ], 697 | "maxDataPoints": 100, 698 | "minSpan": 2, 699 | "nullPointMode": "null", 700 | "nullText": null, 701 | "postfix": "", 702 | "postfixFontSize": "50%", 703 | "prefix": "", 704 | "prefixFontSize": "50%", 705 | "rangeMaps": [ 706 | { 707 | "from": "null", 708 | "text": "N/A", 709 | "to": "null" 710 | } 711 | ], 712 | "sparkline": { 713 | "fillColor": "rgba(31, 118, 189, 0.18)", 714 | "full": false, 715 | "lineColor": "rgb(31, 120, 193)", 716 | "show": true 717 | }, 718 | "tableColumn": "", 719 | "targets": [ 720 | { 721 | "expr": "(((count(count(node_cpu_seconds_total{instance=~\"$node:$port\"}) by (cpu))) - avg(sum by (mode)(irate(node_cpu_seconds_total{mode='idle',instance=~\"$node:$port\"}[5m])))) * 100) / count(count(node_cpu_seconds_total{instance=~\"$node:$port\"}) by (cpu))", 722 | "format": "time_series", 723 | "hide": false, 724 | "interval": "", 725 | "intervalFactor": 1, 726 | "legendFormat": "", 727 | "refId": "A", 728 | "step": 900 729 | } 730 | ], 731 | "thresholds": "85,95", 732 | "title": "CPU Busy", 733 | "type": "singlestat", 734 | "valueFontSize": "80%", 735 | "valueMaps": [ 736 | { 737 | "op": "=", 738 | "text": "N/A", 739 | "value": "null" 740 | } 741 | ], 742 | "valueName": "current" 743 | }, 744 | { 745 | "cacheTimeout": null, 746 | "colorBackground": false, 747 | "colorValue": true, 748 | "colors": [ 749 | "rgba(50, 172, 45, 0.97)", 750 | "rgba(237, 129, 40, 0.89)", 751 | "rgba(245, 54, 54, 0.9)" 752 | ], 753 | "datasource": "${DS_PROMETHEUS}", 754 | "decimals": null, 755 | "description": "Busy state of all CPU cores together (5 min average)", 756 | "format": "percent", 757 | "gauge": { 758 | "maxValue": 100, 759 | "minValue": 0, 760 | "show": true, 761 | "thresholdLabels": false, 762 | "thresholdMarkers": true 763 | }, 764 | "gridPos": { 765 | "h": 4, 766 | "w": 4, 767 | "x": 4, 768 | "y": 7 769 | }, 770 | "id": 155, 771 | "interval": null, 772 | "links": [], 773 | "mappingType": 1, 774 | "mappingTypes": [ 775 | { 776 | "name": "value to text", 777 | "value": 1 778 | }, 779 | { 780 | "name": "range to text", 781 | "value": 2 782 | } 783 | ], 784 | "maxDataPoints": 100, 785 | "minSpan": 2, 786 | "nullPointMode": "null", 787 | "nullText": null, 788 | "postfix": "", 789 | "postfixFontSize": "50%", 790 | "prefix": "", 791 | "prefixFontSize": "50%", 792 | "rangeMaps": [ 793 | { 794 | "from": "null", 795 | "text": "N/A", 796 | "to": "null" 797 | } 798 | ], 799 | "sparkline": { 800 | "fillColor": "rgba(31, 118, 189, 0.18)", 801 | "full": false, 802 | "lineColor": "rgb(31, 120, 193)", 803 | "show": true 804 | }, 805 | "tableColumn": "", 806 | "targets": [ 807 | { 808 | "expr": "avg(node_load5{instance=~\"$node:$port\"}) / count(count(node_cpu_seconds_total{instance=~\"$node:$port\"}) by (cpu)) * 100", 809 | "format": "time_series", 810 | "hide": false, 811 | "intervalFactor": 1, 812 | "refId": "A", 813 | "step": 900 814 | } 815 | ], 816 | "thresholds": "85, 95", 817 | "title": "CPU System Load (5m avg)", 818 | "type": "singlestat", 819 | "valueFontSize": "80%", 820 | "valueMaps": [ 821 | { 822 | "op": "=", 823 | "text": "N/A", 824 | "value": "null" 825 | } 826 | ], 827 | "valueName": "current" 828 | }, 829 | { 830 | "cacheTimeout": null, 831 | "colorBackground": false, 832 | "colorValue": true, 833 | "colors": [ 834 | "rgba(50, 172, 45, 0.97)", 835 | "rgba(237, 129, 40, 0.89)", 836 | "rgba(245, 54, 54, 0.9)" 837 | ], 838 | "datasource": "${DS_PROMETHEUS}", 839 | "decimals": null, 840 | "description": "Busy state of all CPU cores together (1 min average)", 841 | "format": "percent", 842 | "gauge": { 843 | "maxValue": 100, 844 | "minValue": 0, 845 | "show": true, 846 | "thresholdLabels": false, 847 | "thresholdMarkers": true 848 | }, 849 | "gridPos": { 850 | "h": 4, 851 | "w": 4, 852 | "x": 8, 853 | "y": 7 854 | }, 855 | "id": 19, 856 | "interval": null, 857 | "links": [], 858 | "mappingType": 1, 859 | "mappingTypes": [ 860 | { 861 | "name": "value to text", 862 | "value": 1 863 | }, 864 | { 865 | "name": "range to text", 866 | "value": 2 867 | } 868 | ], 869 | "maxDataPoints": 100, 870 | "minSpan": 2, 871 | "nullPointMode": "null", 872 | "nullText": null, 873 | "postfix": "", 874 | "postfixFontSize": "50%", 875 | "prefix": "", 876 | "prefixFontSize": "50%", 877 | "rangeMaps": [ 878 | { 879 | "from": "null", 880 | "text": "N/A", 881 | "to": "null" 882 | } 883 | ], 884 | "sparkline": { 885 | "fillColor": "rgba(31, 118, 189, 0.18)", 886 | "full": false, 887 | "lineColor": "rgb(31, 120, 193)", 888 | "show": true 889 | }, 890 | "tableColumn": "", 891 | "targets": [ 892 | { 893 | "expr": "avg(node_load1{instance=~\"$node:$port\"}) / count(count(node_cpu_seconds_total{instance=~\"$node:$port\"}) by (cpu)) * 100", 894 | "hide": false, 895 | "intervalFactor": 1, 896 | "refId": "A", 897 | "step": 900 898 | } 899 | ], 900 | "thresholds": "85, 95", 901 | "title": "CPU System Load (1m avg)", 902 | "type": "singlestat", 903 | "valueFontSize": "80%", 904 | "valueMaps": [ 905 | { 906 | "op": "=", 907 | "text": "N/A", 908 | "value": "null" 909 | } 910 | ], 911 | "valueName": "current" 912 | }, 913 | { 914 | "aliasColors": { 915 | "Busy": "#EAB839", 916 | "Busy Iowait": "#890F02", 917 | "Busy other": "#1F78C1", 918 | "Idle": "#052B51", 919 | "Idle - Waiting for something to happen": "#052B51", 920 | "guest": "#9AC48A", 921 | "idle": "#052B51", 922 | "iowait": "#EAB839", 923 | "irq": "#BF1B00", 924 | "nice": "#C15C17", 925 | "softirq": "#E24D42", 926 | "steal": "#FCE2DE", 927 | "system": "#508642", 928 | "user": "#5195CE" 929 | }, 930 | "bars": false, 931 | "dashLength": 10, 932 | "dashes": false, 933 | "datasource": "${DS_PROMETHEUS}", 934 | "decimals": 2, 935 | "description": "", 936 | "fill": 4, 937 | "gridPos": { 938 | "h": 8, 939 | "w": 12, 940 | "x": 0, 941 | "y": 11 942 | }, 943 | "id": 77, 944 | "legend": { 945 | "alignAsTable": true, 946 | "avg": true, 947 | "current": true, 948 | "max": true, 949 | "min": true, 950 | "rightSide": false, 951 | "show": true, 952 | "sideWidth": 250, 953 | "sort": "max", 954 | "sortDesc": true, 955 | "total": false, 956 | "values": true 957 | }, 958 | "lines": true, 959 | "linewidth": 1, 960 | "links": [], 961 | "minSpan": 4, 962 | "nullPointMode": "null", 963 | "percentage": true, 964 | "pointradius": 5, 965 | "points": false, 966 | "renderer": "flot", 967 | "seriesOverrides": [], 968 | "spaceLength": 10, 969 | "stack": true, 970 | "steppedLine": false, 971 | "targets": [ 972 | { 973 | "expr": "sum by (instance)(irate(node_cpu_seconds_total{mode=\"system\",instance=~\"$node:$port\"}[5m])) * 100", 974 | "format": "time_series", 975 | "hide": false, 976 | "intervalFactor": 2, 977 | "legendFormat": "System - Processes executing in kernel mode", 978 | "refId": "B", 979 | "step": 120 980 | }, 981 | { 982 | "expr": "sum by (instance)(irate(node_cpu_seconds_total{mode='user',instance=~\"$node:$port\"}[5m])) * 100", 983 | "format": "time_series", 984 | "hide": false, 985 | "intervalFactor": 2, 986 | "legendFormat": "User - Normal processes executing in user mode", 987 | "refId": "D", 988 | "step": 120 989 | }, 990 | { 991 | "expr": "sum by (mode)(irate(node_cpu_seconds_total{mode='idle',instance=~\"$node:$port\"}[5m])) * 100", 992 | "format": "time_series", 993 | "intervalFactor": 2, 994 | "legendFormat": "Idle - Waiting for something to happen", 995 | "refId": "C", 996 | "step": 120 997 | }, 998 | { 999 | "expr": "sum by (mode)(irate(node_cpu_seconds_total{mode='interrupt',instance=~\"$node:$port\"}[5m])) * 100", 1000 | "format": "time_series", 1001 | "intervalFactor": 2, 1002 | "legendFormat": "Interrupt - Servicing interrupts", 1003 | "refId": "E", 1004 | "step": 120 1005 | }, 1006 | { 1007 | "expr": "sum by (mode)(irate(node_cpu_seconds_total{mode='nice',instance=~\"$node:$port\"}[5m])) * 100", 1008 | "format": "time_series", 1009 | "intervalFactor": 2, 1010 | "legendFormat": "Nice - Niced processes executing in user mode", 1011 | "refId": "F", 1012 | "step": 120 1013 | } 1014 | ], 1015 | "thresholds": [], 1016 | "timeFrom": null, 1017 | "timeShift": null, 1018 | "title": "CPU", 1019 | "tooltip": { 1020 | "shared": true, 1021 | "sort": 0, 1022 | "value_type": "individual" 1023 | }, 1024 | "transparent": false, 1025 | "type": "graph", 1026 | "xaxis": { 1027 | "buckets": null, 1028 | "mode": "time", 1029 | "name": null, 1030 | "show": true, 1031 | "values": [] 1032 | }, 1033 | "yaxes": [ 1034 | { 1035 | "format": "short", 1036 | "label": "Percentage", 1037 | "logBase": 1, 1038 | "max": "100", 1039 | "min": "0", 1040 | "show": true 1041 | }, 1042 | { 1043 | "format": "short", 1044 | "label": null, 1045 | "logBase": 1, 1046 | "max": null, 1047 | "min": null, 1048 | "show": false 1049 | } 1050 | ] 1051 | }, 1052 | { 1053 | "aliasColors": { 1054 | "Apps": "#629E51", 1055 | "Buffers": "#614D93", 1056 | "Cache": "#6D1F62", 1057 | "Cached": "#511749", 1058 | "Committed": "#508642", 1059 | "Free": "#0A437C", 1060 | "Harware Corrupted - Amount of RAM that the kernel identified as corrupted / not working": "#CFFAFF", 1061 | "Inactive": "#584477", 1062 | "PageTables": "#0A50A1", 1063 | "Page_Tables": "#0A50A1", 1064 | "RAM_Free": "#E0F9D7", 1065 | "Slab": "#806EB7", 1066 | "Slab_Cache": "#E0752D", 1067 | "Swap": "#BF1B00", 1068 | "Swap - Swap memory usage": "#BF1B00", 1069 | "Swap_Cache": "#C15C17", 1070 | "Swap_Free": "#2F575E", 1071 | "Unused": "#EAB839", 1072 | "Unused - Free memory unasigned": "#052B51" 1073 | }, 1074 | "bars": false, 1075 | "dashLength": 10, 1076 | "dashes": false, 1077 | "datasource": "${DS_PROMETHEUS}", 1078 | "decimals": 2, 1079 | "description": "", 1080 | "fill": 4, 1081 | "gridPos": { 1082 | "h": 8, 1083 | "w": 12, 1084 | "x": 12, 1085 | "y": 11 1086 | }, 1087 | "id": 24, 1088 | "legend": { 1089 | "alignAsTable": true, 1090 | "avg": true, 1091 | "current": true, 1092 | "max": true, 1093 | "min": true, 1094 | "rightSide": false, 1095 | "show": true, 1096 | "sideWidth": 350, 1097 | "sort": "max", 1098 | "sortDesc": true, 1099 | "total": false, 1100 | "values": true 1101 | }, 1102 | "lines": true, 1103 | "linewidth": 1, 1104 | "links": [], 1105 | "minSpan": 4, 1106 | "nullPointMode": "null", 1107 | "percentage": false, 1108 | "pointradius": 5, 1109 | "points": false, 1110 | "renderer": "flot", 1111 | "seriesOverrides": [ 1112 | { 1113 | "alias": "/.*Free.*/", 1114 | "color": "#7EB26D" 1115 | } 1116 | ], 1117 | "spaceLength": 10, 1118 | "stack": true, 1119 | "steppedLine": false, 1120 | "targets": [ 1121 | { 1122 | "expr": "node_memory_inactive_bytes{instance=~\"$node:$port\"}", 1123 | "format": "time_series", 1124 | "hide": false, 1125 | "intervalFactor": 2, 1126 | "legendFormat": "Inactive - Memory that is completely free and ready to use", 1127 | "refId": "A", 1128 | "step": 120 1129 | }, 1130 | { 1131 | "expr": "node_memory_active_bytes{instance=~\"$node:$port\"}", 1132 | "format": "time_series", 1133 | "hide": false, 1134 | "intervalFactor": 2, 1135 | "legendFormat": "Active - Memory currently being used by a process", 1136 | "refId": "C", 1137 | "step": 120 1138 | }, 1139 | { 1140 | "expr": "node_memory_buffer_bytes{instance=~\"$node:$port\"}", 1141 | "format": "time_series", 1142 | "hide": false, 1143 | "intervalFactor": 2, 1144 | "legendFormat": "Buffers - Disk cache", 1145 | "refId": "D", 1146 | "step": 120 1147 | }, 1148 | { 1149 | "expr": "node_memory_cache_bytes{instance=~\"$node:$port\"}", 1150 | "format": "time_series", 1151 | "hide": true, 1152 | "intervalFactor": 2, 1153 | "legendFormat": "Cache - Memory being used to cache data, can be freed immediately if required", 1154 | "refId": "F", 1155 | "step": 120 1156 | }, 1157 | { 1158 | "expr": "node_memory_wired_bytes{instance=~\"$node:$port\"}", 1159 | "format": "time_series", 1160 | "hide": false, 1161 | "intervalFactor": 2, 1162 | "legendFormat": "Wired - Memory in use by the Kernel", 1163 | "refId": "I", 1164 | "step": 120 1165 | }, 1166 | { 1167 | "expr": "node_memory_free_bytes{instance=~\"$node:$port\"}", 1168 | "hide": false, 1169 | "intervalFactor": 2, 1170 | "legendFormat": "Free - Memory that has been freed but is still cached since it may be used again", 1171 | "refId": "E", 1172 | "step": 120 1173 | } 1174 | ], 1175 | "thresholds": [], 1176 | "timeFrom": null, 1177 | "timeShift": null, 1178 | "title": "Memory", 1179 | "tooltip": { 1180 | "shared": true, 1181 | "sort": 0, 1182 | "value_type": "individual" 1183 | }, 1184 | "type": "graph", 1185 | "xaxis": { 1186 | "buckets": null, 1187 | "mode": "time", 1188 | "name": null, 1189 | "show": true, 1190 | "values": [] 1191 | }, 1192 | "yaxes": [ 1193 | { 1194 | "format": "bytes", 1195 | "label": "Bytes", 1196 | "logBase": 1, 1197 | "max": null, 1198 | "min": "0", 1199 | "show": true 1200 | }, 1201 | { 1202 | "format": "short", 1203 | "label": null, 1204 | "logBase": 1, 1205 | "max": null, 1206 | "min": null, 1207 | "show": false 1208 | } 1209 | ] 1210 | }, 1211 | { 1212 | "aliasColors": { 1213 | "receive_packets_eth0": "#7EB26D", 1214 | "receive_packets_lo": "#E24D42", 1215 | "transmit_packets_eth0": "#7EB26D", 1216 | "transmit_packets_lo": "#E24D42" 1217 | }, 1218 | "bars": false, 1219 | "dashLength": 10, 1220 | "dashes": false, 1221 | "datasource": "${DS_PROMETHEUS}", 1222 | "fill": 4, 1223 | "gridPos": { 1224 | "h": 8, 1225 | "w": 12, 1226 | "x": 0, 1227 | "y": 19 1228 | }, 1229 | "id": 84, 1230 | "legend": { 1231 | "alignAsTable": true, 1232 | "avg": true, 1233 | "current": true, 1234 | "max": true, 1235 | "min": true, 1236 | "rightSide": false, 1237 | "show": true, 1238 | "total": false, 1239 | "values": true 1240 | }, 1241 | "lines": true, 1242 | "linewidth": 1, 1243 | "links": [], 1244 | "nullPointMode": "null", 1245 | "percentage": false, 1246 | "pointradius": 5, 1247 | "points": false, 1248 | "renderer": "flot", 1249 | "seriesOverrides": [ 1250 | { 1251 | "alias": "/.*Trans.*/", 1252 | "transform": "negative-Y" 1253 | }, 1254 | { 1255 | "alias": "/.*lo0.*/", 1256 | "color": "#7EB26D" 1257 | }, 1258 | { 1259 | "alias": "/.*lo1.*/", 1260 | "color": "#EAB839" 1261 | }, 1262 | { 1263 | "alias": "/.*em0.*/", 1264 | "color": "#EF843C" 1265 | }, 1266 | { 1267 | "alias": "/.*em1.*/", 1268 | "color": "#E24D42" 1269 | } 1270 | ], 1271 | "spaceLength": 10, 1272 | "stack": false, 1273 | "steppedLine": false, 1274 | "targets": [ 1275 | { 1276 | "expr": "irate(node_network_receive_bytes_total{instance=~\"$node:$port\"}[5m])", 1277 | "format": "time_series", 1278 | "intervalFactor": 2, 1279 | "legendFormat": "{{device}} - Receive", 1280 | "refId": "O", 1281 | "step": 120 1282 | }, 1283 | { 1284 | "expr": "irate(node_network_transmit_bytes_total{instance=~\"$node:$port\"}[5m])", 1285 | "format": "time_series", 1286 | "intervalFactor": 2, 1287 | "legendFormat": "{{device}} - Transmit", 1288 | "refId": "P", 1289 | "step": 120 1290 | } 1291 | ], 1292 | "thresholds": [], 1293 | "timeFrom": null, 1294 | "timeShift": null, 1295 | "title": "Network Traffic", 1296 | "tooltip": { 1297 | "shared": true, 1298 | "sort": 0, 1299 | "value_type": "individual" 1300 | }, 1301 | "type": "graph", 1302 | "xaxis": { 1303 | "buckets": null, 1304 | "mode": "time", 1305 | "name": null, 1306 | "show": true, 1307 | "values": [] 1308 | }, 1309 | "yaxes": [ 1310 | { 1311 | "format": "Bps", 1312 | "label": "Bytes out (-) / in (+)", 1313 | "logBase": 1, 1314 | "max": null, 1315 | "min": null, 1316 | "show": true 1317 | }, 1318 | { 1319 | "format": "short", 1320 | "label": null, 1321 | "logBase": 1, 1322 | "max": null, 1323 | "min": null, 1324 | "show": false 1325 | } 1326 | ] 1327 | }, 1328 | { 1329 | "aliasColors": {}, 1330 | "bars": false, 1331 | "dashLength": 10, 1332 | "dashes": false, 1333 | "datasource": "${DS_PROMETHEUS}", 1334 | "decimals": 3, 1335 | "description": "", 1336 | "fill": 4, 1337 | "gridPos": { 1338 | "h": 8, 1339 | "w": 12, 1340 | "x": 12, 1341 | "y": 19 1342 | }, 1343 | "height": "", 1344 | "id": 156, 1345 | "legend": { 1346 | "alignAsTable": true, 1347 | "avg": true, 1348 | "current": true, 1349 | "max": true, 1350 | "min": true, 1351 | "rightSide": false, 1352 | "show": true, 1353 | "sort": "max", 1354 | "sortDesc": true, 1355 | "total": false, 1356 | "values": true 1357 | }, 1358 | "lines": true, 1359 | "linewidth": 1, 1360 | "links": [], 1361 | "minSpan": 4, 1362 | "nullPointMode": "null", 1363 | "percentage": false, 1364 | "pointradius": 5, 1365 | "points": false, 1366 | "renderer": "flot", 1367 | "seriesOverrides": [ 1368 | { 1369 | "alias": "/.*Used.*/", 1370 | "transform": "negative-Y" 1371 | }, 1372 | { 1373 | "alias": "/.*Used.*/", 1374 | "color": "#E24D42" 1375 | }, 1376 | { 1377 | "alias": "/.*Available.*/", 1378 | "color": "#7EB26D" 1379 | } 1380 | ], 1381 | "spaceLength": 10, 1382 | "stack": false, 1383 | "steppedLine": false, 1384 | "targets": [ 1385 | { 1386 | "expr": "node_filesystem_size_bytes{instance=~\"$node:$port\"} - node_filesystem_free_bytes{instance=~\"$node:$port\"}", 1387 | "format": "time_series", 1388 | "intervalFactor": 2, 1389 | "legendFormat": "{{device}} - {{mountpoint}} - Used", 1390 | "refId": "A", 1391 | "step": 120 1392 | }, 1393 | { 1394 | "expr": "node_filesystem_free_bytes{instance=~\"$node:$port\"}", 1395 | "format": "time_series", 1396 | "hide": true, 1397 | "intervalFactor": 2, 1398 | "legendFormat": "{{device}} - {{mountpoint}} - Free", 1399 | "refId": "B", 1400 | "step": 30 1401 | }, 1402 | { 1403 | "expr": "node_filesystem_avail_bytes{instance=~\"$node:$port\"}", 1404 | "format": "time_series", 1405 | "intervalFactor": 2, 1406 | "legendFormat": "{{device}} - {{mountpoint}} - Available", 1407 | "refId": "C", 1408 | "step": 120 1409 | } 1410 | ], 1411 | "thresholds": [], 1412 | "timeFrom": null, 1413 | "timeShift": null, 1414 | "title": "Disk Space Available / Used", 1415 | "tooltip": { 1416 | "shared": true, 1417 | "sort": 0, 1418 | "value_type": "individual" 1419 | }, 1420 | "type": "graph", 1421 | "xaxis": { 1422 | "buckets": null, 1423 | "mode": "time", 1424 | "name": null, 1425 | "show": true, 1426 | "values": [] 1427 | }, 1428 | "yaxes": [ 1429 | { 1430 | "format": "bytes", 1431 | "label": "Bytes used (-) / available (+)", 1432 | "logBase": 1, 1433 | "max": null, 1434 | "min": null, 1435 | "show": true 1436 | }, 1437 | { 1438 | "format": "short", 1439 | "label": null, 1440 | "logBase": 1, 1441 | "max": null, 1442 | "min": null, 1443 | "show": false 1444 | } 1445 | ] 1446 | }, 1447 | { 1448 | "aliasColors": {}, 1449 | "bars": false, 1450 | "dashLength": 10, 1451 | "dashes": false, 1452 | "datasource": "${DS_PROMETHEUS}", 1453 | "decimals": 3, 1454 | "fill": 2, 1455 | "gridPos": { 1456 | "h": 5, 1457 | "w": 24, 1458 | "x": 0, 1459 | "y": 27 1460 | }, 1461 | "id": 7, 1462 | "legend": { 1463 | "alignAsTable": false, 1464 | "avg": true, 1465 | "current": true, 1466 | "max": true, 1467 | "min": true, 1468 | "show": true, 1469 | "total": false, 1470 | "values": true 1471 | }, 1472 | "lines": true, 1473 | "linewidth": 1, 1474 | "links": [], 1475 | "minSpan": 4, 1476 | "nullPointMode": "null", 1477 | "percentage": false, 1478 | "pointradius": 5, 1479 | "points": false, 1480 | "renderer": "flot", 1481 | "repeat": null, 1482 | "seriesOverrides": [], 1483 | "spaceLength": 10, 1484 | "stack": false, 1485 | "steppedLine": false, 1486 | "targets": [ 1487 | { 1488 | "expr": "node_load1{instance=~\"$node:$port\"}", 1489 | "format": "time_series", 1490 | "intervalFactor": 4, 1491 | "legendFormat": "Load 1m", 1492 | "refId": "A", 1493 | "step": 480 1494 | }, 1495 | { 1496 | "expr": "node_load5{instance=~\"$node:$port\"}", 1497 | "format": "time_series", 1498 | "intervalFactor": 4, 1499 | "legendFormat": "Load 5m", 1500 | "refId": "B", 1501 | "step": 480 1502 | }, 1503 | { 1504 | "expr": "node_load15{instance=~\"$node:$port\"}", 1505 | "format": "time_series", 1506 | "intervalFactor": 4, 1507 | "legendFormat": "Load 15m", 1508 | "refId": "C", 1509 | "step": 480 1510 | } 1511 | ], 1512 | "thresholds": [], 1513 | "timeFrom": null, 1514 | "timeShift": null, 1515 | "title": "System Load", 1516 | "tooltip": { 1517 | "shared": true, 1518 | "sort": 0, 1519 | "value_type": "individual" 1520 | }, 1521 | "type": "graph", 1522 | "xaxis": { 1523 | "buckets": null, 1524 | "mode": "time", 1525 | "name": null, 1526 | "show": true, 1527 | "values": [] 1528 | }, 1529 | "yaxes": [ 1530 | { 1531 | "format": "short", 1532 | "label": "Load", 1533 | "logBase": 1, 1534 | "max": null, 1535 | "min": "0", 1536 | "show": true 1537 | }, 1538 | { 1539 | "format": "short", 1540 | "label": null, 1541 | "logBase": 1, 1542 | "max": null, 1543 | "min": null, 1544 | "show": false 1545 | } 1546 | ] 1547 | }, 1548 | { 1549 | "collapsed": false, 1550 | "gridPos": { 1551 | "h": 1, 1552 | "w": 24, 1553 | "x": 0, 1554 | "y": 32 1555 | }, 1556 | "id": 242, 1557 | "panels": [], 1558 | "repeat": null, 1559 | "title": "Network Traffic Detail", 1560 | "type": "row" 1561 | }, 1562 | { 1563 | "aliasColors": { 1564 | "receive_packets_eth0": "#7EB26D", 1565 | "receive_packets_lo": "#E24D42", 1566 | "transmit_packets_eth0": "#7EB26D", 1567 | "transmit_packets_lo": "#E24D42" 1568 | }, 1569 | "bars": false, 1570 | "dashLength": 10, 1571 | "dashes": false, 1572 | "datasource": "${DS_PROMETHEUS}", 1573 | "fill": 4, 1574 | "gridPos": { 1575 | "h": 8, 1576 | "w": 12, 1577 | "x": 0, 1578 | "y": 33 1579 | }, 1580 | "id": 232, 1581 | "legend": { 1582 | "alignAsTable": true, 1583 | "avg": true, 1584 | "current": true, 1585 | "max": true, 1586 | "min": true, 1587 | "rightSide": false, 1588 | "show": true, 1589 | "sort": "max", 1590 | "sortDesc": true, 1591 | "total": false, 1592 | "values": true 1593 | }, 1594 | "lines": true, 1595 | "linewidth": 1, 1596 | "links": [], 1597 | "nullPointMode": "null", 1598 | "percentage": false, 1599 | "pointradius": 5, 1600 | "points": false, 1601 | "renderer": "flot", 1602 | "seriesOverrides": [ 1603 | { 1604 | "alias": "/.*Trans.*/", 1605 | "transform": "negative-Y" 1606 | }, 1607 | { 1608 | "alias": "/.*lo0.*/", 1609 | "color": "#7EB26D" 1610 | }, 1611 | { 1612 | "alias": "/.*lo1.*/", 1613 | "color": "#EAB839" 1614 | }, 1615 | { 1616 | "alias": "/.*em0.*/", 1617 | "color": "#EF843C" 1618 | }, 1619 | { 1620 | "alias": "/.*em1.*/", 1621 | "color": "#E24D42" 1622 | } 1623 | ], 1624 | "spaceLength": 10, 1625 | "stack": false, 1626 | "steppedLine": false, 1627 | "targets": [ 1628 | { 1629 | "expr": "irate(node_network_transmit_packets_total{instance=~\"$node:$port\"}[5m])", 1630 | "format": "time_series", 1631 | "intervalFactor": 2, 1632 | "legendFormat": "{{device}} - Transmit packets", 1633 | "refId": "P", 1634 | "step": 240 1635 | }, 1636 | { 1637 | "expr": "irate(node_network_receive_packets_total{instance=~\"$node:$port\"}[5m])", 1638 | "format": "time_series", 1639 | "intervalFactor": 2, 1640 | "legendFormat": "{{device}} - Receive packets", 1641 | "refId": "B", 1642 | "step": 240 1643 | } 1644 | ], 1645 | "thresholds": [], 1646 | "timeFrom": null, 1647 | "timeShift": null, 1648 | "title": "Network Traffic Packets", 1649 | "tooltip": { 1650 | "shared": true, 1651 | "sort": 0, 1652 | "value_type": "individual" 1653 | }, 1654 | "type": "graph", 1655 | "xaxis": { 1656 | "buckets": null, 1657 | "mode": "time", 1658 | "name": null, 1659 | "show": true, 1660 | "values": [] 1661 | }, 1662 | "yaxes": [ 1663 | { 1664 | "format": "pps", 1665 | "label": "Packets out (-) / in (+)", 1666 | "logBase": 1, 1667 | "max": null, 1668 | "min": null, 1669 | "show": true 1670 | }, 1671 | { 1672 | "format": "short", 1673 | "label": null, 1674 | "logBase": 1, 1675 | "max": null, 1676 | "min": null, 1677 | "show": false 1678 | } 1679 | ] 1680 | }, 1681 | { 1682 | "aliasColors": { 1683 | "receive_packets_eth0": "#7EB26D", 1684 | "receive_packets_lo": "#E24D42", 1685 | "transmit_packets_eth0": "#7EB26D", 1686 | "transmit_packets_lo": "#E24D42" 1687 | }, 1688 | "bars": false, 1689 | "dashLength": 10, 1690 | "dashes": false, 1691 | "datasource": "${DS_PROMETHEUS}", 1692 | "fill": 4, 1693 | "gridPos": { 1694 | "h": 8, 1695 | "w": 12, 1696 | "x": 12, 1697 | "y": 33 1698 | }, 1699 | "id": 231, 1700 | "legend": { 1701 | "alignAsTable": true, 1702 | "avg": true, 1703 | "current": true, 1704 | "max": true, 1705 | "min": true, 1706 | "rightSide": false, 1707 | "show": true, 1708 | "total": false, 1709 | "values": true 1710 | }, 1711 | "lines": true, 1712 | "linewidth": 1, 1713 | "links": [], 1714 | "nullPointMode": "null", 1715 | "percentage": false, 1716 | "pointradius": 5, 1717 | "points": false, 1718 | "renderer": "flot", 1719 | "seriesOverrides": [ 1720 | { 1721 | "alias": "/.*Trans.*/", 1722 | "transform": "negative-Y" 1723 | }, 1724 | { 1725 | "alias": "/.*lo0.*/", 1726 | "color": "#7EB26D" 1727 | }, 1728 | { 1729 | "alias": "/.*lo1.*/", 1730 | "color": "#EAB839" 1731 | }, 1732 | { 1733 | "alias": "/.*em0.*/", 1734 | "color": "#EF843C" 1735 | }, 1736 | { 1737 | "alias": "/.*em1.*/", 1738 | "color": "#E24D42" 1739 | } 1740 | ], 1741 | "spaceLength": 10, 1742 | "stack": false, 1743 | "steppedLine": false, 1744 | "targets": [ 1745 | { 1746 | "expr": "irate(node_network_transmit_multicast_total{instance=~\"$node:$port\"}[5m])", 1747 | "format": "time_series", 1748 | "intervalFactor": 2, 1749 | "legendFormat": "{{device}} - Transmit multicast", 1750 | "refId": "P", 1751 | "step": 240 1752 | }, 1753 | { 1754 | "expr": "irate(node_network_receive_multicast_total{instance=~\"$node:$port\"}[5m])", 1755 | "format": "time_series", 1756 | "intervalFactor": 2, 1757 | "legendFormat": "{{device}} - Receive multicast", 1758 | "refId": "B", 1759 | "step": 240 1760 | } 1761 | ], 1762 | "thresholds": [], 1763 | "timeFrom": null, 1764 | "timeShift": null, 1765 | "title": "Network Traffic Multicast", 1766 | "tooltip": { 1767 | "shared": true, 1768 | "sort": 0, 1769 | "value_type": "individual" 1770 | }, 1771 | "type": "graph", 1772 | "xaxis": { 1773 | "buckets": null, 1774 | "mode": "time", 1775 | "name": null, 1776 | "show": true, 1777 | "values": [] 1778 | }, 1779 | "yaxes": [ 1780 | { 1781 | "format": "pps", 1782 | "label": "Packets out (-) / in (+)", 1783 | "logBase": 1, 1784 | "max": null, 1785 | "min": null, 1786 | "show": true 1787 | }, 1788 | { 1789 | "format": "short", 1790 | "label": null, 1791 | "logBase": 1, 1792 | "max": null, 1793 | "min": null, 1794 | "show": false 1795 | } 1796 | ] 1797 | }, 1798 | { 1799 | "aliasColors": { 1800 | "receive_packets_eth0": "#7EB26D", 1801 | "receive_packets_lo": "#E24D42", 1802 | "transmit_packets_eth0": "#7EB26D", 1803 | "transmit_packets_lo": "#E24D42" 1804 | }, 1805 | "bars": false, 1806 | "dashLength": 10, 1807 | "dashes": false, 1808 | "datasource": "${DS_PROMETHEUS}", 1809 | "fill": 4, 1810 | "gridPos": { 1811 | "h": 8, 1812 | "w": 12, 1813 | "x": 0, 1814 | "y": 41 1815 | }, 1816 | "id": 230, 1817 | "legend": { 1818 | "alignAsTable": true, 1819 | "avg": true, 1820 | "current": true, 1821 | "max": true, 1822 | "min": true, 1823 | "rightSide": false, 1824 | "show": true, 1825 | "total": false, 1826 | "values": true 1827 | }, 1828 | "lines": true, 1829 | "linewidth": 1, 1830 | "links": [], 1831 | "nullPointMode": "null", 1832 | "percentage": false, 1833 | "pointradius": 5, 1834 | "points": false, 1835 | "renderer": "flot", 1836 | "seriesOverrides": [ 1837 | { 1838 | "alias": "/.*Trans.*/", 1839 | "transform": "negative-Y" 1840 | }, 1841 | { 1842 | "alias": "/.*lo0.*/", 1843 | "color": "#7EB26D" 1844 | }, 1845 | { 1846 | "alias": "/.*lo1.*/", 1847 | "color": "#EAB839" 1848 | }, 1849 | { 1850 | "alias": "/.*em0.*/", 1851 | "color": "#EF843C" 1852 | }, 1853 | { 1854 | "alias": "/.*em1.*/", 1855 | "color": "#E24D42" 1856 | } 1857 | ], 1858 | "spaceLength": 10, 1859 | "stack": false, 1860 | "steppedLine": false, 1861 | "targets": [ 1862 | { 1863 | "expr": "irate(node_network_transmit_errs_total{instance=~\"$node:$port\"}[5m])", 1864 | "format": "time_series", 1865 | "intervalFactor": 2, 1866 | "legendFormat": "{{device}} - Transmit errors", 1867 | "refId": "P", 1868 | "step": 240 1869 | }, 1870 | { 1871 | "expr": "irate(node_network_receive_errs_total{instance=~\"$node:$port\"}[5m])", 1872 | "format": "time_series", 1873 | "intervalFactor": 2, 1874 | "legendFormat": "{{device}} - Receive errors", 1875 | "refId": "B", 1876 | "step": 240 1877 | } 1878 | ], 1879 | "thresholds": [], 1880 | "timeFrom": null, 1881 | "timeShift": null, 1882 | "title": "Network Traffic Errors", 1883 | "tooltip": { 1884 | "shared": true, 1885 | "sort": 0, 1886 | "value_type": "individual" 1887 | }, 1888 | "type": "graph", 1889 | "xaxis": { 1890 | "buckets": null, 1891 | "mode": "time", 1892 | "name": null, 1893 | "show": true, 1894 | "values": [] 1895 | }, 1896 | "yaxes": [ 1897 | { 1898 | "format": "pps", 1899 | "label": "Packets out (-) / in (+)", 1900 | "logBase": 1, 1901 | "max": null, 1902 | "min": null, 1903 | "show": true 1904 | }, 1905 | { 1906 | "format": "short", 1907 | "label": null, 1908 | "logBase": 1, 1909 | "max": null, 1910 | "min": null, 1911 | "show": false 1912 | } 1913 | ] 1914 | }, 1915 | { 1916 | "aliasColors": { 1917 | "receive_packets_eth0": "#7EB26D", 1918 | "receive_packets_lo": "#E24D42", 1919 | "transmit_packets_eth0": "#7EB26D", 1920 | "transmit_packets_lo": "#E24D42" 1921 | }, 1922 | "bars": false, 1923 | "dashLength": 10, 1924 | "dashes": false, 1925 | "datasource": "${DS_PROMETHEUS}", 1926 | "fill": 4, 1927 | "gridPos": { 1928 | "h": 8, 1929 | "w": 12, 1930 | "x": 12, 1931 | "y": 41 1932 | }, 1933 | "id": 229, 1934 | "legend": { 1935 | "alignAsTable": true, 1936 | "avg": true, 1937 | "current": true, 1938 | "max": true, 1939 | "min": true, 1940 | "rightSide": false, 1941 | "show": true, 1942 | "total": false, 1943 | "values": true 1944 | }, 1945 | "lines": true, 1946 | "linewidth": 1, 1947 | "links": [], 1948 | "nullPointMode": "null", 1949 | "percentage": false, 1950 | "pointradius": 5, 1951 | "points": false, 1952 | "renderer": "flot", 1953 | "seriesOverrides": [ 1954 | { 1955 | "alias": "/.*Trans.*/", 1956 | "transform": "negative-Y" 1957 | }, 1958 | { 1959 | "alias": "/.*lo0.*/", 1960 | "color": "#7EB26D" 1961 | }, 1962 | { 1963 | "alias": "/.*lo1.*/", 1964 | "color": "#EAB839" 1965 | }, 1966 | { 1967 | "alias": "/.*em0.*/", 1968 | "color": "#EF843C" 1969 | }, 1970 | { 1971 | "alias": "/.*em1.*/", 1972 | "color": "#E24D42" 1973 | } 1974 | ], 1975 | "spaceLength": 10, 1976 | "stack": false, 1977 | "steppedLine": false, 1978 | "targets": [ 1979 | { 1980 | "expr": "irate(node_network_receive_drop_total{instance=~\"$node:$port\"}[5m])", 1981 | "format": "time_series", 1982 | "intervalFactor": 2, 1983 | "legendFormat": "{{device}} - Receive drop", 1984 | "refId": "O", 1985 | "step": 240 1986 | }, 1987 | { 1988 | "expr": "irate(node_network_transmit_drop_total{instance=~\"$node:$port\"}[5m])", 1989 | "format": "time_series", 1990 | "intervalFactor": 2, 1991 | "legendFormat": "{{device}} - Transmit drop", 1992 | "refId": "A", 1993 | "step": 240 1994 | } 1995 | ], 1996 | "thresholds": [], 1997 | "timeFrom": null, 1998 | "timeShift": null, 1999 | "title": "Network Traffic Drops", 2000 | "tooltip": { 2001 | "shared": true, 2002 | "sort": 0, 2003 | "value_type": "individual" 2004 | }, 2005 | "type": "graph", 2006 | "xaxis": { 2007 | "buckets": null, 2008 | "mode": "time", 2009 | "name": null, 2010 | "show": true, 2011 | "values": [] 2012 | }, 2013 | "yaxes": [ 2014 | { 2015 | "format": "pps", 2016 | "label": "Packets out (-) / in (+)", 2017 | "logBase": 1, 2018 | "max": null, 2019 | "min": null, 2020 | "show": true 2021 | }, 2022 | { 2023 | "format": "short", 2024 | "label": null, 2025 | "logBase": 1, 2026 | "max": null, 2027 | "min": null, 2028 | "show": false 2029 | } 2030 | ] 2031 | } 2032 | ], 2033 | "refresh": "1m", 2034 | "schemaVersion": 16, 2035 | "style": "dark", 2036 | "tags": [], 2037 | "templating": { 2038 | "list": [ 2039 | { 2040 | "allValue": null, 2041 | "current": {}, 2042 | "datasource": "${DS_PROMETHEUS}", 2043 | "hide": 0, 2044 | "includeAll": false, 2045 | "label": "Host:", 2046 | "multi": false, 2047 | "name": "node", 2048 | "options": [], 2049 | "query": "label_values(node_time_seconds{job=\"node\"}, instance)", 2050 | "refresh": 1, 2051 | "regex": "/([^:]+):.*/", 2052 | "sort": 0, 2053 | "tagValuesQuery": "", 2054 | "tags": [], 2055 | "tagsQuery": "", 2056 | "type": "query", 2057 | "useTags": false 2058 | }, 2059 | { 2060 | "allValue": null, 2061 | "current": {}, 2062 | "datasource": "${DS_PROMETHEUS}", 2063 | "hide": 2, 2064 | "includeAll": false, 2065 | "label": "port", 2066 | "multi": false, 2067 | "name": "port", 2068 | "options": [], 2069 | "query": "label_values(node_time_seconds, instance)", 2070 | "refresh": 1, 2071 | "regex": "/[^:]+:(.*)/", 2072 | "sort": 0, 2073 | "tagValuesQuery": "", 2074 | "tags": [], 2075 | "tagsQuery": "", 2076 | "type": "query", 2077 | "useTags": false 2078 | } 2079 | ] 2080 | }, 2081 | "time": { 2082 | "from": "now-30m", 2083 | "to": "now" 2084 | }, 2085 | "timepicker": { 2086 | "refresh_intervals": [ 2087 | "5s", 2088 | "10s", 2089 | "30s", 2090 | "1m", 2091 | "5m", 2092 | "15m", 2093 | "30m", 2094 | "1h", 2095 | "2h", 2096 | "1d" 2097 | ], 2098 | "time_options": [ 2099 | "5m", 2100 | "15m", 2101 | "1h", 2102 | "6h", 2103 | "12h", 2104 | "24h", 2105 | "2d", 2106 | "7d", 2107 | "30d" 2108 | ] 2109 | }, 2110 | "timezone": "browser", 2111 | "title": "OpenBSD Nodes", 2112 | "uid": "DlnEe4Wmz", 2113 | "version": 12 2114 | } --------------------------------------------------------------------------------