Updated and coverted

Updated and coverted my fork from evilsocket/pwnagotchi master branch to aluminum-ice/pwnagotchi master branch

removed hannadiamond repository

changed pwnagotchi community plugin repository to my pwnagotchi community plugin repository

removed mastodon plugin

removed screenrc configuration

cloned pwnagotchi community plugin repository only once

removed configure pwnagotchi for the custom plugin directory from builder/pwnagotchi.yml

reconfigured auto-update to point to the scifijunkie repo

edited main.custom_plugins to point to /usr/local/share/pwnagotchi/custom-plugins in pwnagotchi/defaults.toml

removed mastodon configuration from defaults.toml

removed ntfy configuration from defaults.toml

removed handshakes-m.py from default plugin

removed mastodon.py from default plugin

removed ntfy.py from default plugin

addressed [ERROR] [update] 'tag_name'

addressed rate limit exceeded

addressed TypeError: Descriptors cannot not be created directly.

Reran pip-compile
This commit is contained in:
scifijunkie 2024-05-23 12:37:59 -05:00
parent c34aed94c0
commit eb77f29135
32 changed files with 1131 additions and 453 deletions

View File

@ -1,9 +1,10 @@
exclude *.pyc .DS_Store .gitignore MANIFEST.in exclude *.pyc .DS_Store .gitignore MANIFEST.in
include requirements.txt
include setup.py include setup.py
include distribute_setup.py
include README.md include README.md
include LICENSE include LICENSE
recursive-include bin * recursive-include bin *
recursive-include builder/data *
recursive-include pwnagotchi *.py recursive-include pwnagotchi *.py
recursive-include pwnagotchi *.yml recursive-include pwnagotchi *.yml
recursive-include pwnagotchi *.* recursive-include pwnagotchi *.*

View File

@ -1,6 +1,31 @@
PACKER_VERSION=1.7.8 PACKER_VERSION := 1.8.3
PWN_HOSTNAME=pwnagotchi PWN_HOSTNAME := pwnagotchi
PWN_VERSION=master # PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py)
PWN_VERSION := $(or ${PWN_VERSION},$(shell cut -d"'" -f2 < pwnagotchi/_version.py))
PWN_RELEASE := pwnagotchi-$(PWN_VERSION)
MACHINE_TYPE := $(shell uname -m)
ifneq (,$(filter x86_64,$(MACHINE_TYPE)))
GOARCH := amd64
else ifneq (,$(filter i686,$(MACHINE_TYPE)))
GOARCH := 386
else ifneq (,$(filter arm64% aarch64%,$(MACHINE_TYPE)))
GOARCH := arm64
else ifneq (,$(filter arm%,$(MACHINE_TYPE)))
GOARCH := arm
else
GOARCH := amd64
$(warning Unable to detect CPU arch from machine type $(MACHINE_TYPE), assuming $(GOARCH))
endif
# The Ansible part of the build can inadvertently change the active hostname of
# the build machine while updating the permanent hostname of the build image.
# If the unshare command is available, use it to create a separate namespace
# so hostname changes won't affect the build machine.
UNSHARE := $(shell command -v unshare)
ifneq (,$(UNSHARE))
UNSHARE := $(UNSHARE) --uts
endif
all: clean install image all: clean install image
@ -8,23 +33,47 @@ langs:
@for lang in pwnagotchi/locale/*/; do\ @for lang in pwnagotchi/locale/*/; do\
echo "compiling language: $$lang ..."; \ echo "compiling language: $$lang ..."; \
./scripts/language.sh compile $$(basename $$lang); \ ./scripts/language.sh compile $$(basename $$lang); \
done done
install: install:
curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip PACKER := /tmp/pwnagotchi/packer
unzip /tmp/packer.zip -d /tmp PACKER_URL := https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_$(GOARCH).zip
sudo mv /tmp/packer /usr/bin/packer $(PACKER):
git clone https://github.com/solo-io/packer-builder-arm-image /tmp/packer-builder-arm-image mkdir -p $(@D)
cd /tmp/packer-builder-arm-image && go get -d ./... && go build curl -L "$(PACKER_URL)" -o $(PACKER).zip
sudo cp /tmp/packer-builder-arm-image/packer-plugin-arm-image /usr/bin unzip $(PACKER).zip -d $(@D)
rm $(PACKER).zip
chmod +x $@
image: SDIST := dist/pwnagotchi-$(PWN_VERSION).tar.gz
cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json $(SDIST): setup.py pwnagotchi
sudo mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img python3 setup.py sdist
sudo sha256sum pwnagotchi-raspbian-lite-$(PWN_VERSION).img > pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256
sudo zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 pwnagotchi-raspbian-lite-$(PWN_VERSION).img # Building the image requires packer, but don't rebuild the image just because packer updated.
$(PWN_RELEASE).img: | $(PACKER)
# If the packer or ansible files are updated, rebuild the image.
$(PWN_RELEASE).img: $(SDIST) builder/pwnagotchi.json builder/pwnagotchi.yml $(shell find builder/data -type f)
sudo $(PACKER) plugins install github.com/solo-io/arm-image
cd builder && sudo $(UNSHARE) $(PACKER) build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json
sudo chown -R $$USER:$$USER builder/output-pwnagotchi
mv builder/output-pwnagotchi/image $@
# If any of these files are updated, rebuild the checksums.
$(PWN_RELEASE).sha256: $(PWN_RELEASE).img
sha256sum $^ > $@
# If any of the input files are updated, rebuild the archive.
$(PWN_RELEASE).zip: $(PWN_RELEASE).img $(PWN_RELEASE).sha256
zip $(PWN_RELEASE).zip $^
.PHONY: image
image: $(PWN_RELEASE).zip
clean: clean:
sudo rm -rf /tmp/packer-builder-arm-image - python3 setup.py clean --all
sudo rm -f pwnagotchi-raspbian-lite-*.zip pwnagotchi-raspbian-lite-*.img pwnagotchi-raspbian-lite-*.sha256 - rm -rf dist pwnagotchi.egg-info
sudo rm -rf builder/output-pwnagotchi builder/packer_cache - rm -f $(PACKER)
- rm -f $(PWN_RELEASE).*
- sudo rm -rf builder/output-pwnagotchi builder/packer_cache

View File

@ -1,2 +1,4 @@
allow-hotplug wlan0 allow-hotplug wlan0
iface wlan0 inet static iface wlan0 inet manual
pre-up ifconfig $IFACE up
post-down ifconfig $IFACE down

View File

@ -19,7 +19,12 @@ if ! check_brcm; then
fi fi
# start mon0 # start mon0
start_monitor_interface if ! is_interface_up 'mon0'; then
start_monitor_interface
else
stop_monitor_interface
start_monitor_interface
fi
if is_auto_mode_no_delete; then if is_auto_mode_no_delete; then
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0

View File

@ -3,12 +3,30 @@
# well ... it blinks the led # well ... it blinks the led
blink_led() { blink_led() {
for i in $(seq 1 "$1"); do for i in $(seq 1 "$1"); do
echo 0 >/sys/class/leds/led0/brightness if [ -d /sys/class/leds/led0 ]
then
echo 0 | tee /sys/class/leds/led0/brightness
else
echo 0 | tee /sys/class/leds/ACT/brightness
fi
sleep 0.3 sleep 0.3
echo 1 >/sys/class/leds/led0/brightness
if [ -d /sys/class/leds/led0 ]
then
echo 1 | tee /sys/class/leds/led0/brightness
else
echo 1 | tee /sys/class/leds/ACT/brightness
fi
sleep 0.3 sleep 0.3
done done
echo 0 >/sys/class/leds/led0/brightness
if [ -d /sys/class/leds/led0 ]
then
echo 0 | tee /sys/class/leds/led0/brightness
else
echo 0 | tee /sys/class/leds/ACT/brightness
fi
sleep 0.3 sleep 0.3
} }
@ -33,20 +51,31 @@ reload_brcm() {
# starts mon0 # starts mon0
start_monitor_interface() { start_monitor_interface() {
rfkill unblock all
iw dev wlan0 set power_save off
ifconfig wlan0 up
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add mon0 type monitor && ifconfig mon0 up iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add mon0 type monitor && ifconfig mon0 up
# If wlan0 is NOT taken down after bringing up mon0, then when switching to AUTO you will get:
# error 400: error while initializing mon0 to channel 1: iw: out=command failed: Device or resource busy (-16) err=exit status 240
ifconfig wlan0 down
} }
# stops mon0 # stops mon0
stop_monitor_interface() { stop_monitor_interface() {
ifconfig mon0 down && iw dev mon0 del ifconfig mon0 down && iw dev mon0 del
ifconfig wlan0 up
} }
# returns 0 if the specificed network interface is up # returns 0 if the specificed network interface is up
is_interface_up() { is_interface_up() {
if grep -qi 'up' /sys/class/net/$1/operstate; then if grep -qi 'up' /sys/class/net/$1/operstate; then
return 0 return 0
else
return 1
fi fi
return 1
} }
# returns 0 if conditions for AUTO mode are met # returns 0 if conditions for AUTO mode are met

View File

@ -3,95 +3,34 @@
{ {
"name": "pwnagotchi", "name": "pwnagotchi",
"type": "arm-image", "type": "arm-image",
"iso_url": "https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2020-02-14/2020-02-13-raspbian-buster-lite.zip", "iso_url": "https://downloads.raspberrypi.org/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-05-03/2023-05-03-raspios-buster-armhf-lite.img.xz",
"iso_checksum": "12ae6e17bf95b6ba83beca61e7394e7411b45eba7e6a520f434b0748ea7370e8", "iso_checksum": "3d210e61b057de4de90eadb46e28837585a9b24247c221998f5bead04f88624c",
"target_image_size": 6442450944 "target_image_size": 9368709120,
"qemu_args": ["-cpu", "arm1176"]
} }
], ],
"provisioners": [ "provisioners": [
{ {
"type": "shell", "type": "shell",
"inline": [ "inline": [
"sed -i 's/^\\([^#]\\)/#\\1/g' /etc/ld.so.preload", "mv /etc/ld.so.preload /etc/ld.so.preload.DISABLED",
"uname -a",
"dpkg-architecture", "dpkg-architecture",
"apt -y update --allow-releaseinfo-change", "mkdir -p /usr/local/src/pwnagotchi"
"apt install -y ansible"
] ]
}, },
{ {
"type": "file", "type": "file",
"source": "data/usr/bin/pwnlib", "sources": [
"destination": "/usr/bin/pwnlib" "../dist/pwnagotchi-{{user `pwn_version`}}.tar.gz"
}, ],
{ "destination": "/usr/local/src/pwnagotchi/"
"type": "file",
"source": "data/usr/bin/bettercap-launcher",
"destination": "/usr/bin/bettercap-launcher"
},
{
"type": "file",
"source": "data/usr/bin/pwnagotchi-launcher",
"destination": "/usr/bin/pwnagotchi-launcher"
},
{
"type": "file",
"source": "data/usr/bin/monstop",
"destination": "/usr/bin/monstop"
},
{
"type": "file",
"source": "data/usr/bin/monstart",
"destination": "/usr/bin/monstart"
},
{
"type": "file",
"source": "data/usr/bin/hdmion",
"destination": "/usr/bin/hdmion"
},
{
"type": "file",
"source": "data/usr/bin/hdmioff",
"destination": "/usr/bin/hdmioff"
},
{
"type": "file",
"source": "data/etc/network/interfaces.d/lo-cfg",
"destination": "/etc/network/interfaces.d/lo-cfg"
},
{
"type": "file",
"source": "data/etc/network/interfaces.d/wlan0-cfg",
"destination": "/etc/network/interfaces.d/wlan0-cfg"
},
{
"type": "file",
"source": "data/etc/network/interfaces.d/usb0-cfg",
"destination": "/etc/network/interfaces.d/usb0-cfg"
},
{
"type": "file",
"source": "data/etc/network/interfaces.d/eth0-cfg",
"destination": "/etc/network/interfaces.d/eth0-cfg"
},
{
"type": "file",
"source": "data/etc/systemd/system/pwngrid-peer.service",
"destination": "/etc/systemd/system/pwngrid-peer.service"
},
{
"type": "file",
"source": "data/etc/systemd/system/pwnagotchi.service",
"destination": "/etc/systemd/system/pwnagotchi.service"
},
{
"type": "file",
"source": "data/etc/systemd/system/bettercap.service",
"destination": "/etc/systemd/system/bettercap.service"
}, },
{ {
"type": "shell", "type": "shell",
"inline": [ "inline": [
"chmod +x /usr/bin/*" "apt-get -y --allow-releaseinfo-change update",
"apt-get install -y --no-install-recommends ansible"
] ]
}, },
{ {
@ -103,7 +42,7 @@
{ {
"type": "shell", "type": "shell",
"inline": [ "inline": [
"sed -i 's/^#\\(.+\\)/\\1/g' /etc/ld.so.preload" "mv /etc/ld.so.preload.DISABLED /etc/ld.so.preload"
] ]
} }
] ]

View File

@ -1,18 +1,17 @@
--- ---
- hosts: - hosts:
- 127.0.0.1 - 127.0.0.1
gather_facts: yes
become: yes become: yes
vars: vars:
pwnagotchi: pwnagotchi:
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}" hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
version: "{{ lookup('env', 'PWN_VERSION') | default('master', true) }}" version: "{{ lookup('env', 'PWN_VERSION') | default('master', true) }}"
system: system:
boot_options4: boot_options:
- "dtoverlay=disable-wifi"
- "arm_freq=800"
boot_optionsall:
- "dtoverlay=dwc2" - "dtoverlay=dwc2"
- "dtoverlay=spi1-3cs" - "dtoverlay=spi1-3cs"
- "dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4"
- "dtparam=spi=on" - "dtparam=spi=on"
- "dtparam=i2c_arm=on" - "dtparam=i2c_arm=on"
- "dtparam=i2c1=on" - "dtparam=i2c1=on"
@ -39,7 +38,8 @@
- dnsmasq.service - dnsmasq.service
packages: packages:
bettercap: bettercap:
url: "https://github.com/bettercap/bettercap/releases/download/v2.31.0/bettercap_linux_armhf_v2.31.0.zip" # We will install bettercap v2.32 from source
# url: "https://github.com/bettercap/bettercap/releases/download/v2.31.0/bettercap_linux_armhf_v2.31.0.zip"
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
pwngrid: pwngrid:
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.3/pwngrid_linux_armhf_v1.10.3.zip" url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.3/pwngrid_linux_armhf_v1.10.3.zip"
@ -56,12 +56,13 @@
- triggerhappy - triggerhappy
- wpa_supplicant - wpa_supplicant
- nfs-common - nfs-common
# Remove every golang package because we will install go-1.20.2
- golang*
- python2* - python2*
install: install:
- rsync - rsync
- vim - vim
- screen - screen
- golang
- git - git
- build-essential - build-essential
- python3-pip - python3-pip
@ -72,6 +73,7 @@
- libopenmpi-dev - libopenmpi-dev
- libatlas-base-dev - libatlas-base-dev
- libjasper-dev - libjasper-dev
- libgtk-3-0
- libqtgui4 - libqtgui4
- libqt4-test - libqt4-test
- libopenjp2-7 - libopenjp2-7
@ -89,10 +91,6 @@
- libnetfilter-queue-dev - libnetfilter-queue-dev
- libopenmpi3 - libopenmpi3
- dphys-swapfile - dphys-swapfile
- kalipi-kernel
- kalipi-bootloader
- kalipi-re4son-firmware
- kalipi-kernel-headers
- libraspberrypi0 - libraspberrypi0
- libraspberrypi-dev - libraspberrypi-dev
- libraspberrypi-doc - libraspberrypi-doc
@ -109,10 +107,33 @@
- fonts-ipaexfont-gothic - fonts-ipaexfont-gothic
- cryptsetup - cryptsetup
- dnsmasq - dnsmasq
- python3-rpi.gpio - aircrack-ng
- firmware-ralink - raspberrypi-kernel-headers
- libgmp3-dev
- qpdf
- bison
- flex
- make
- autoconf
- libtool
- texinfo
- binutils
- lnav
- p7zip-full
environment:
ARCHFLAGS: "-arch armv7l"
tasks: tasks:
- name: System details
debug:
msg="{{ item }}"
with_items:
- "{{ ansible_distribution }}"
- "{{ ansible_distribution_version }}"
- "{{ ansible_distribution_major_version }}"
- "{{ ansible_architecture }}"
- "{{ ansible_machine }}"
- name: change hostname - name: change hostname
hostname: hostname:
name: "{{pwnagotchi.hostname}}" name: "{{pwnagotchi.hostname}}"
@ -134,26 +155,6 @@
line: 'ExecStart=/usr/lib/bluetooth/bluetoothd --noplugin=sap' line: 'ExecStart=/usr/lib/bluetooth/bluetoothd --noplugin=sap'
state: present state: present
- name: Add re4son-kernel repo key
apt_key:
keyserver: pgp.mit.edu
id: 11764EE8AC24832F
- name: Add re4son-kernel repository
apt_repository:
repo: deb http://http.re4son-kernel.com/re4son/ kali-pi main
state: present
- name: create /etc/apt/preferences.d/kali.pref
copy:
dest: /etc/apt/preferences.d/kali.pref
force: yes
content: |
# ensure kali packages that are installed take precedence
Package: *
Pin: release n=kali-pi
Pin-Priority: 999
- name: add firmware packages to hold - name: add firmware packages to hold
dpkg_selections: dpkg_selections:
name: "{{ item }}" name: "{{ item }}"
@ -172,17 +173,26 @@
- name: upgrade apt distro - name: upgrade apt distro
apt: apt:
upgrade: full upgrade: dist
- name: install packages - name: install packages
apt: apt:
name: "{{ packages.apt.install }}" name: "{{ packages.apt.install }}"
state: present state: present
- name: Update .bashrc (root)
blockinfile:
dest: /root/.bashrc
state: present
block: |
export MAKEFLAGS=-j$(nproc)
insertafter: EOF
- name: configure dphys-swapfile - name: configure dphys-swapfile
file: lineinfile:
path: /etc/dphys-swapfile path: /etc/dphys-swapfile
content: "CONF_SWAPSIZE=1024" regexp: "^CONF_SWAPSIZE=.*$"
line: "CONF_SWAPSIZE=512"
- name: clone papirus repository - name: clone papirus repository
git: git:
@ -214,67 +224,55 @@
regexp: "#EPD_SIZE=2.0" regexp: "#EPD_SIZE=2.0"
line: "EPD_SIZE=2.0" line: "EPD_SIZE=2.0"
- name: collect python pip package list - name: Delete papirus content & directory
command: "pip3 list"
register: pip_output
- name: set python pip package facts
set_fact:
pip_packages: >
{{ pip_packages | default({}) | combine( { item.split()[0]: item.split()[1] } ) }}
with_items: "{{ pip_output.stdout_lines }}"
- name: acquire python3 pip target
command: "python3 -c 'import sys;print(sys.path.pop())'"
register: pip_target
- name: clone pwnagotchi repository
git:
repo: https://git.chadwaltercummings.me/scifijunkie/pwnagotchi.git
dest: /usr/local/src/pwnagotchi
register: pwnagotchigit
- name: create /usr/local/share/pwnagotchi/ folder
file: file:
path: /usr/local/share/pwnagotchi/ state: absent
path: /usr/local/src/gratis
when: gratisgit.changed
# pip v20.3 uses a newer dependency resolver that better handles our unique situation.
# Specifically, it handles mismatches between direct requirements without extras and
# indirect requirements that do want extras (e.g. gym vs stable-baselines->gym[atari]).
- name: Upgrade pip and install rpi-hardware-pwm
pip:
name:
- pip>=20.3
- rpi-hardware-pwm
# We need the --ignore-installed option so that pip simply overwrites/upgrades existing
# packages instead of trying to uninstall them first. While this sounds dangerous,
# this matches the legacy behavior of pip. This is required to prevent pip from trying
# (and failing) to uninstall python packages that were originally installed via apt.
- name: Install pwnagotchi from source archive
pip:
name: /usr/local/src/pwnagotchi/pwnagotchi-{{ pwnagotchi.version }}.tar.gz
extra_args: --verbose --prefer-binary --ignore-installed --retries 50 --index-url https://nexus.chadwaltercummings.me/repository/pypi.org/simple --extra-index-url https://nexus.chadwaltercummings.me/repository/www.piwheels.org/simple
- name: create custom plugin directory
file:
path: /usr/local/share/pwnagotchi/custom-plugins/
state: directory state: directory
- name: clone pwnagotchi plugins repository - name: clone pwnagotchi plugins repository
git: git:
repo: https://git.chadwaltercummings.me/scifijunkie/pwnagotchi-plugins-contrib.git repo: https://git.chadwaltercummings.me/scifijunkie/pwnagotchi-plugins-contrib.git
dest: /usr/local/share/pwnagotchi/availaible-plugins dest: /usr/local/share/pwnagotchi/available-plugins
- name: fetch pwnagotchi version - name: Copy aircrackonly.py
set_fact: copy:
pwnagotchi_version: "{{ lookup('file', '/usr/local/src/pwnagotchi/pwnagotchi/_version.py') | regex_replace('.*__version__.*=.*''([0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*)''.*', '\\1') }}" src: /usr/local/share/pwnagotchi/available-plugins/aircrackonly.py
dest: /usr/local/share/pwnagotchi/custom-plugins/aircrackonly.py
owner: root
group: root
mode: '644'
- name: pwnagotchi version found - name: Copy handshakes-dl.py
debug: copy:
msg: "{{ pwnagotchi_version }}" src: /usr/local/share/pwnagotchi/available-plugins/handshakes-dl.py
dest: /usr/local/share/pwnagotchi/custom-plugins/handshakes-dl.py
- name: build pwnagotchi wheel owner: root
command: "python3 setup.py sdist bdist_wheel" group: root
args: mode: '644'
chdir: /usr/local/src/pwnagotchi
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
- name: install opencv-python
pip:
name: "https://www.piwheels.org/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl"
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
when: (pip_packages['opencv-python'] is undefined) or (pip_packages['opencv-python'] != '3.4.3.18')
- name: install tensorflow
pip:
name: "https://www.piwheels.org/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl"
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
when: (pip_packages['tensorflow'] is undefined) or (pip_packages['tensorflow'] != '1.13.1')
- name: install pwnagotchi wheel and dependencies
pip:
name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
extra_args: "--no-cache-dir"
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
- name: download and install pwngrid - name: download and install pwngrid
unarchive: unarchive:
@ -283,15 +281,33 @@
remote_src: yes remote_src: yes
mode: 0755 mode: 0755
- name: download and install bettercap # Install go-1.21.5
- name: Install go-1.21.5
unarchive: unarchive:
src: "{{ packages.bettercap.url }}" src: https://go.dev/dl/go1.21.5.linux-armv6l.tar.gz
dest: /usr/bin dest: /usr/local
remote_src: yes remote_src: yes
exclude: register: golang
- README.md
- LICENSE.md - name: Update .bashrc for go-1.21.5 (pi)
mode: 0755 blockinfile:
dest: /home/pi/.bashrc
state: present
block: |
export GOPATH=$HOME/go
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
insertafter: EOF
when: golang.changed
- name: Install bettercap v2.32
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go env -w GO111MODULE=off && go get github.com/bettercap/bettercap && cd $GOPATH/src/github.com/bettercap/bettercap && make build && make install"
args:
executable: /bin/bash
register: bettercap
- name: Link bettercap v2.32
command: ln -s /usr/local/bin/bettercap /usr/bin/bettercap
when: bettercap.changed
- name: clone bettercap caplets - name: clone bettercap caplets
git: git:
@ -312,6 +328,230 @@
remote_src: yes remote_src: yes
mode: 0755 mode: 0755
# Install nexmon to fix wireless scanning (takes 2.5G of space)
- name: clone nexmon repository
git:
repo: https://github.com/seemoo-lab/nexmon.git
dest: /usr/local/src/nexmon
# version: bfb3fe90c881498d7ee245b38f16722c1de26fa1
register: nexmongit
- name: configure libisl
command: chdir=/usr/local/src/nexmon/buildtools/isl-0.10/ ./configure
- name: make libisl
command: chdir=/usr/local/src/nexmon/buildtools/isl-0.10/ make
- name: install libisl
command: chdir=/usr/local/src/nexmon/buildtools/isl-0.10/ make install
- name: link libisl
command: ln -s /usr/local/lib/libisl.so /usr/lib/arm-linux-gnueabihf/libisl.so.10
- name: autoreconf libmpfr
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ autoreconf -f -i
- name: configure libmpfr
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ ./configure
- name: make libmpfr
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ make
- name: install libmpfr
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ make install
- name: link libmpfr
command: ln -s /usr/local/lib/libmpfr.so /usr/lib/arm-linux-gnueabihf/libmpfr.so.4
- name: make firmware
shell: "source ./setup_env.sh && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
- name: choose the right kernel version (bcm43436b0)
replace:
dest: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/Makefile
backup: no
regexp: "KERNEL_VERSION = .*$"
replace: "KERNEL_VERSION = 5.10"
- name: choose the right kernel release (variable) (bcm43436b0)
lineinfile:
dest: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/Makefile
insertafter: "DRIVER_FOLDER_NAME = .*$"
line: "KERNEL_RELEASE = 5.10.103-v7+"
- name: choose the right kernel release (replace string) (bcm43436b0)
replace:
dest: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/Makefile
backup: no
regexp: "shell uname -r"
replace: "KERNEL_RELEASE"
- name: make firmware patch (bcm43436b0)
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
# - name: backup original firmware
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make backup-firmware"
# args:
# executable: /bin/bash
# chdir: /usr/local/src/nexmon/
# - name: install new firmware
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make install-firmware"
# args:
# executable: /bin/bash
# chdir: /usr/local/src/nexmon/
- name: install new firmware (bcm43436b0)
copy:
src: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/brcmfmac43436-sdio.bin
dest: /lib/firmware/brcm/brcmfmac43436-sdio.bin
- name: choose the right kernel version (bcm43430a1)
replace:
dest: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/Makefile
backup: no
regexp: "KERNEL_VERSION = .*$"
replace: "KERNEL_VERSION = 5.10"
- name: choose the right kernel release (variable) (bcm43430a1)
lineinfile:
dest: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/Makefile
insertafter: "DRIVER_FOLDER_NAME = .*$"
line: "KERNEL_RELEASE = 5.10.103-v7+"
- name: choose the right kernel release (replace string) (bcm43430a1)
replace:
dest: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/Makefile
backup: no
regexp: "shell uname -r"
replace: "KERNEL_RELEASE"
- name: make firmware patch (bcm43430a1)
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
# - name: backup original firmware
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make backup-firmware"
# args:
# executable: /bin/bash
# chdir: /usr/local/src/nexmon/
# - name: install new firmware
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make install-firmware"
# args:
# executable: /bin/bash
# chdir: /usr/local/src/nexmon/
- name: install new firmware (bcm43430a1)
copy:
src: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/brcmfmac43430-sdio.bin
dest: /lib/firmware/brcm/brcmfmac43430-sdio.bin
- name: Delete the firmware blob to avoid it crashing
file:
state: absent
path: /lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
- name: Delete the RPiZW firmware blob to avoid it crashing
file:
state: absent
path: /lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
- name: Delete the RPi3 firmware blob to avoid it crashing
file:
state: absent
path: /lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob
- name: choose the right kernel version (bcm43455c0)
replace:
dest: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/Makefile
backup: no
regexp: "KERNEL_VERSION = .*$"
replace: "KERNEL_VERSION = 5.10"
- name: choose the right kernel release (variable) (bcm43455c0)
lineinfile:
dest: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/Makefile
insertafter: "DRIVER_FOLDER_NAME = .*$"
line: "KERNEL_RELEASE = 5.10.103-v7+"
- name: choose the right kernel release (replace string) (bcm43455c0)
replace:
dest: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/Makefile
backup: no
regexp: "shell uname -r"
replace: "KERNEL_RELEASE"
- name: make firmware patch (bcm43455c0)
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
# - name: backup original firmware
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make backup-firmware"
# args:
# executable: /bin/bash
# chdir: /usr/local/src/nexmon/
# - name: install new firmware
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make install-firmware"
# args:
# executable: /bin/bash
# chdir: /usr/local/src/nexmon/
- name: install new firmware (bcm43455c0)
copy:
src: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/brcmfmac43455-sdio.bin
dest: /lib/firmware/brcm/brcmfmac43455-sdio.bin
- name: make nexutil
command: chdir=/usr/local/src/nexmon/utilities/nexutil/ make
- name: make install nexutil
command: chdir=/usr/local/src/nexmon/utilities/nexutil/ make install
# - name: copy modified driver
# shell: "cd /usr/local/src/nexmon/patches/driver/brcmfmac_5.10.y-nexmon/ && cp brcmfmac.ko /lib/modules/5.10.103-v7+/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko && depmod -a"
# args:
# executable: /bin/bash
- name: copy modified driver (everyone but RPiZW)
copy:
src: /usr/local/src/nexmon/patches/driver/brcmfmac_5.10.y-nexmon/brcmfmac.ko
dest: /lib/modules/5.10.103-v7+/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko
- name: ensure depmod runs on reboot to load modified driver (brcmfmac)
lineinfile:
dest: /etc/rc.local
line: "/sbin/depmod -a"
# To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation
- name: Delete nexmon content & directory
file:
state: absent
path: /usr/local/src/nexmon/
- name: Add pwnlog alias
lineinfile:
dest: /home/pi/.bashrc
line: "\nalias pwnlog='tail -f -n300 /var/log/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
insertafter: EOF
- name: install bettercap caplets
make:
chdir: /tmp/caplets
target: install
when: capletsgit.changed
- name: add HDMI powersave to rc.local - name: add HDMI powersave to rc.local
blockinfile: blockinfile:
path: /etc/rc.local path: /etc/rc.local
@ -341,24 +581,41 @@
# ui.display.type = "waveshare_2" # ui.display.type = "waveshare_2"
when: not user_config.stat.exists when: not user_config.stat.exists
# - name: append commented out parameters for usb_hat_c.py
# lineinfile:
# dest: /etc/pwnagotchi/config.toml
# line: "# main.plugins.ups_hat_c.enabled = true\n# main.plugins.ups_hat_c.label_on = true # show BAT label or just percentage\n# main.plugins.ups_hat_c.shutdown = 5 # battery percent at which the device will turn off\n# main.plugins.ups_hat_c.bat_x_coord = 140\n# main.plugins.ups_hat_c.bat_y_coord = 0"
# insertafter: EOF
#bizzarely changing the plugin code directly reverts to the old string
- name: Reconfigure auto-update to point to the scifijunk repo
replace:
dest: /usr/local/lib/python3.7/dist-packages/pwnagotchi/plugins/default/auto-update.py
backup: no
regexp: "evilsocket/pwnagotchi"
replace: "scifijunk/pwnagotchi"
- name: Delete unnecessary large folder to save space (/root/go)
file:
state: absent
path: /root/go
- name: Delete unnecessary large folder to save space (/root/.cache)
file:
state: absent
path: /root/.cache
- name: enable ssh on boot - name: enable ssh on boot
file: file:
path: /boot/ssh path: /boot/ssh
state: touch state: touch
- name: adjust [pi4] /boot/config.txt
lineinfile:
dest: /boot/config.txt
insertafter: max_framebuffers=2
line: '{{ item }}'
with_items: "{{system.boot_options4}}"
- name: adjust [all] /boot/config.txt - name: adjust /boot/config.txt
lineinfile: lineinfile:
dest: /boot/config.txt dest: /boot/config.txt
insertafter: EOF insertafter: EOF
line: '{{ item }}' line: '{{ item }}'
with_items: "{{system.boot_optionsall}}" with_items: "{{system.boot_options}}"
- name: adjust /etc/modules - name: adjust /etc/modules
lineinfile: lineinfile:
@ -415,9 +672,14 @@
You learn more about me at https://pwnagotchi.ai/ You learn more about me at https://pwnagotchi.ai/
when: hostname.changed when: hostname.changed
# Ansible's apt module has an "autoclean" option but it only removes packages
# that can no longer be downloaded. Ansible v2.13 added the "clean" option
# which actually purges the apt cache, but that's newer than what we can
# install from the RasPiOS repos. Instead, we'll manually clean the cache.
- name: clean apt cache - name: clean apt cache
apt: command: "apt-get clean"
autoclean: yes args:
warn: false
- name: remove dependencies that are no longer required - name: remove dependencies that are no longer required
apt: apt:

View File

@ -41,7 +41,7 @@ def set_name(new_name):
fp.write(patched) fp.write(patched)
os.system("hostname '%s'" % new_name) os.system("hostname '%s'" % new_name)
reboot() pwnagotchi.reboot()
def name(): def name():
@ -119,7 +119,7 @@ def shutdown():
from pwnagotchi import fs from pwnagotchi import fs
for m in fs.mounts: for m in fs.mounts:
m.sync() m.sync()
os.system("sync") os.system("sync")
os.system("halt") os.system("halt")
@ -133,6 +133,7 @@ def restart(mode):
os.system("touch /root/.pwnagotchi-manual") os.system("touch /root/.pwnagotchi-manual")
os.system("service bettercap restart") os.system("service bettercap restart")
time.sleep(2)
os.system("service pwnagotchi restart") os.system("service pwnagotchi restart")

View File

@ -1 +1 @@
__version__ = '1.5.5' __version__='1.8.4'

View File

@ -324,6 +324,12 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
found_handshake = False found_handshake = False
jmsg = json.loads(msg) jmsg = json.loads(msg)
# give plugins access to all raw bettercap events
try:
plugins.on('bcap_%s' % re.sub(r"[^a-z0-9_]+", "_", jmsg['tag'].lower()), self, jmsg)
except Exception as err:
logging.error("Processing event: %s" % err)
if jmsg['tag'] == 'wifi.client.handshake': if jmsg['tag'] == 'wifi.client.handshake':
filename = jmsg['data']['file'] filename = jmsg['data']['file']
sta_mac = jmsg['data']['station'] sta_mac = jmsg['data']['station']

View File

@ -2,9 +2,20 @@ import json
import logging import logging
import requests import requests
import websockets import websockets
import asyncio
import random
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
from time import sleep
requests.adapters.DEFAULT_RETRIES = 5 # increase retries number
ping_timeout = 180
ping_interval = 15
max_queue = 10000
min_sleep = 0.5
max_sleep = 5.0
def decode(r, verbose_errors=True): def decode(r, verbose_errors=True):
try: try:
@ -31,25 +42,78 @@ class Client(object):
self.websocket = "ws://%s:%s@%s:%d/api" % (username, password, hostname, port) self.websocket = "ws://%s:%s@%s:%d/api" % (username, password, hostname, port)
self.auth = HTTPBasicAuth(username, password) self.auth = HTTPBasicAuth(username, password)
def session(self): # session takes optional argument to pull a sub-dictionary
r = requests.get("%s/session" % self.url, auth=self.auth) # ex.: "session/wifi", "session/ble"
def session(self, sess="session"):
r = requests.get("%s/%s" % (self.url, sess), auth=self.auth)
return decode(r) return decode(r)
async def start_websocket(self, consumer): async def start_websocket(self, consumer):
s = "%s/events" % self.websocket s = "%s/events" % self.websocket
# More modern version of the approach below
# logging.info("Creating new websocket...")
# async for ws in websockets.connect(s):
# try:
# async for msg in ws:
# try:
# await consumer(msg)
# except Exception as ex:
# logging.debug("Error while parsing event (%s)", ex)
# except websockets.exceptions.ConnectionClosedError:
# sleep_time = max_sleep*random.random()
# logging.warning('Retrying websocket connection in {} sec'.format(sleep_time))
# await asyncio.sleep(sleep_time)
# continue
# restarted every time the connection fails
while True: while True:
try: logging.info("creating new websocket...")
async with websockets.connect(s, ping_interval=60, ping_timeout=90) as ws: try:
async for msg in ws: async with websockets.connect(s, ping_interval=ping_interval, ping_timeout=ping_timeout, max_queue=max_queue) as ws:
# listener loop
while True:
try: try:
await consumer(msg) async for msg in ws:
except Exception as ex: try:
logging.debug("Error while parsing event (%s)", ex) await consumer(msg)
except websockets.exceptions.ConnectionClosedError: except Exception as ex:
logging.debug("Lost websocket connection. Reconnecting...") logging.debug("error while parsing event (%s)", ex)
except websockets.exceptions.WebSocketException as wex: except websockets.exceptions.ConnectionClosedError:
logging.debug("Websocket exception (%s)", wex) try:
pong = await ws.ping()
await asyncio.wait_for(pong, timeout=ping_timeout)
logging.warning('ping OK, keeping connection alive...')
continue
except:
sleep_time = min_sleep + max_sleep*random.random()
logging.warning('ping error - retrying connection in {} sec'.format(sleep_time))
await asyncio.sleep(sleep_time)
break
except ConnectionRefusedError:
sleep_time = min_sleep + max_sleep*random.random()
logging.warning('nobody seems to be listening at the bettercap endpoint...')
logging.warning('retrying connection in {} sec'.format(sleep_time))
await asyncio.sleep(sleep_time)
continue
except OSError:
sleep_time = min_sleep + max_sleep*random.random()
logging.warning('connection to the bettercap endpoint failed...')
logging.warning('retrying connection in {} sec'.format(sleep_time))
await asyncio.sleep(sleep_time)
continue
def run(self, command, verbose_errors=True): def run(self, command, verbose_errors=True):
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command}) while True:
try:
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})
except requests.exceptions.ConnectionError as e:
sleep_time = min_sleep + max_sleep*random.random()
logging.warning("can't run my request... connection to the bettercap endpoint failed...")
logging.warning('retrying run in {} sec'.format(sleep_time))
sleep(sleep_time)
else:
break
return decode(r, verbose_errors=verbose_errors) return decode(r, verbose_errors=verbose_errors)

View File

@ -1,9 +1,9 @@
main.name = "" main.name = ""
main.lang = "en" main.lang = "en"
main.confd = "/etc/pwnagotchi/conf.d/" main.confd = "/etc/pwnagotchi/conf.d/"
main.custom_plugins = "" main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins"
main.custom_plugin_repos = [ main.custom_plugin_repos = [
"https://github.com/evilsocket/pwnagotchi-plugins-contrib/archive/master.zip" "https://git.chadwaltercummings.me/scifijunkie/pwnagotchi-plugins-contrib.git"
] ]
main.iface = "mon0" main.iface = "mon0"
main.mon_start_cmd = "/usr/bin/monstart" main.mon_start_cmd = "/usr/bin/monstart"
@ -18,6 +18,8 @@ main.whitelist = [
] ]
main.filter = "" main.filter = ""
main.log.debug = false
main.plugins.grid.enabled = true main.plugins.grid.enabled = true
main.plugins.grid.report = false main.plugins.grid.report = false
main.plugins.grid.exclude = [ main.plugins.grid.exclude = [
@ -33,7 +35,7 @@ main.plugins.net-pos.api_key = "test"
main.plugins.gps.enabled = false main.plugins.gps.enabled = false
main.plugins.gps.speed = 19200 main.plugins.gps.speed = 19200
main.plugins.gps.device = "/dev/ttyUSB0" main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947"
main.plugins.webgpsmap.enabled = false main.plugins.webgpsmap.enabled = false
@ -47,6 +49,7 @@ main.plugins.wpa-sec.enabled = false
main.plugins.wpa-sec.api_key = "" main.plugins.wpa-sec.api_key = ""
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org" main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
main.plugins.wpa-sec.download_results = false main.plugins.wpa-sec.download_results = false
main.plugins.wpa-sec.download_interval = 3600
main.plugins.wpa-sec.whitelist = [] main.plugins.wpa-sec.whitelist = []
main.plugins.wigle.enabled = false main.plugins.wigle.enabled = false
@ -83,7 +86,10 @@ main.plugins.memtemp.scale = "celsius"
main.plugins.memtemp.orientation = "horizontal" main.plugins.memtemp.orientation = "horizontal"
main.plugins.paw-gps.enabled = false main.plugins.paw-gps.enabled = false
main.plugins.paw-gps.ip = "" main.plugins.paw-gps.ip = "192.168.44.1:8080"
main.plugins.ups_lite.enabled = false
main.plugins.ups_lite.shutdown = 2
main.plugins.gpio_buttons.enabled = false main.plugins.gpio_buttons.enabled = false

View File

@ -221,7 +221,7 @@ def setup_logging(args, config):
formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s") formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s")
root = logging.getLogger() root = logging.getLogger()
root.setLevel(logging.DEBUG if args.debug else logging.INFO) root.setLevel(logging.DEBUG if args.debug or cfg['debug']==True else logging.INFO)
if filename: if filename:
# since python default log rotation might break session data in different files, # since python default log rotation might break session data in different files,
@ -307,3 +307,5 @@ def do_rotate(filename, stats, cfg):
with open(log_filename, 'rb') as src: with open(log_filename, 'rb') as src:
with gzip.open(archive_filename, 'wb') as dst: with gzip.open(archive_filename, 'wb') as dst:
dst.writelines(src) dst.writelines(src)
os.remove(log_filename)

View File

@ -1,17 +1,17 @@
import os import os
import glob import glob
import _thread
import threading import threading
import importlib, importlib.util import importlib, importlib.util
import logging import logging
from concurrent.futures import ThreadPoolExecutor
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default") default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
loaded = {} loaded = {}
database = {} database = {}
locks = {} locks = {}
THREAD_POOL_SIZE = 10
executor = ThreadPoolExecutor(max_workers=THREAD_POOL_SIZE)
class Plugin: class Plugin:
@classmethod @classmethod
@ -30,7 +30,6 @@ class Plugin:
if cb is not None and callable(cb): if cb is not None and callable(cb):
locks["%s::%s" % (plugin_name, attr_name)] = threading.Lock() locks["%s::%s" % (plugin_name, attr_name)] = threading.Lock()
def toggle_plugin(name, enable=True): def toggle_plugin(name, enable=True):
""" """
Load or unload a plugin Load or unload a plugin
@ -69,12 +68,10 @@ def toggle_plugin(name, enable=True):
return False return False
def on(event_name, *args, **kwargs): def on(event_name, *args, **kwargs):
for plugin_name in loaded.keys(): for plugin_name in loaded.keys():
one(plugin_name, event_name, *args, **kwargs) one(plugin_name, event_name, *args, **kwargs)
def locked_cb(lock_name, cb, *args, **kwargs): def locked_cb(lock_name, cb, *args, **kwargs):
global locks global locks
@ -84,7 +81,6 @@ def locked_cb(lock_name, cb, *args, **kwargs):
with locks[lock_name]: with locks[lock_name]:
cb(*args, *kwargs) cb(*args, *kwargs)
def one(plugin_name, event_name, *args, **kwargs): def one(plugin_name, event_name, *args, **kwargs):
global loaded global loaded
@ -96,12 +92,11 @@ def one(plugin_name, event_name, *args, **kwargs):
try: try:
lock_name = "%s::%s" % (plugin_name, cb_name) lock_name = "%s::%s" % (plugin_name, cb_name)
locked_cb_args = (lock_name, callback, *args, *kwargs) locked_cb_args = (lock_name, callback, *args, *kwargs)
_thread.start_new_thread(locked_cb, locked_cb_args) executor.submit(locked_cb, *locked_cb_args)
except Exception as e: except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True) logging.error(e, exc_info=True)
def load_from_file(filename): def load_from_file(filename):
logging.debug("loading %s" % filename) logging.debug("loading %s" % filename)
plugin_name = os.path.basename(filename.replace(".py", "")) plugin_name = os.path.basename(filename.replace(".py", ""))
@ -110,7 +105,6 @@ def load_from_file(filename):
spec.loader.exec_module(instance) spec.loader.exec_module(instance)
return plugin_name, instance return plugin_name, instance
def load_from_path(path, enabled=()): def load_from_path(path, enabled=()):
global loaded, database global loaded, database
logging.debug("loading plugins from %s - enabled: %s" % (path, enabled)) logging.debug("loading plugins from %s - enabled: %s" % (path, enabled))
@ -126,7 +120,6 @@ def load_from_path(path, enabled=()):
return loaded return loaded
def load(config): def load(config):
enabled = [name for name, options in config['main']['plugins'].items() if enabled = [name for name, options in config['main']['plugins'].items() if
'enabled' in options and options['enabled']] 'enabled' in options and options['enabled']]

View File

@ -10,7 +10,7 @@ from pwnagotchi.utils import download_file, unzip, save_config, parse_version, m
from pwnagotchi.plugins import default_path from pwnagotchi.plugins import default_path
SAVE_DIR = '/usr/local/share/pwnagotchi/availaible-plugins/' SAVE_DIR = '/usr/local/share/pwnagotchi/available-plugins/'
DEFAULT_INSTALL_PATH = '/usr/local/share/pwnagotchi/installed-plugins/' DEFAULT_INSTALL_PATH = '/usr/local/share/pwnagotchi/installed-plugins/'

View File

@ -7,14 +7,15 @@ import platform
import shutil import shutil
import glob import glob
from threading import Lock from threading import Lock
import time
import pwnagotchi import pwnagotchi
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile, parse_version as version_to_tuple from pwnagotchi.utils import StatusFile, parse_version as version_to_tuple
def check(version, repo, native=True): def check_remote_version(version, repo, native=True):
logging.debug("checking remote version for %s, local is %s" % (repo, version)) logging.debug("Checking remote version for %s, local is %s" % (repo, version))
info = { info = {
'repo': repo, 'repo': repo,
'current': version, 'current': version,
@ -24,81 +25,92 @@ def check(version, repo, native=True):
'arch': platform.machine() 'arch': platform.machine()
} }
resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo) try:
latest = resp.json() resp = requests.get(f"https://api.github.com/repos/{repo}/releases/latest")
info['available'] = latest_ver = latest['tag_name'].replace('v', '') resp.raise_for_status()
is_arm = info['arch'].startswith('arm') latest = resp.json()
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
local = version_to_tuple(info['current']) is_arm = info['arch'].startswith('arm')
remote = version_to_tuple(latest_ver) local = version_to_tuple(info['current'])
if remote > local: remote = version_to_tuple(latest_ver)
if not native:
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name']) if remote > local:
else: if not native:
# check if this release is compatible with arm6 info['url'] = f"https://github.com/{repo}/archive/{latest['tag_name']}.zip"
for asset in latest['assets']: else:
download_url = asset['browser_download_url'] for asset in latest['assets']:
if download_url.endswith('.zip') and ( download_url = asset['browser_download_url']
info['arch'] in download_url or (is_arm and 'armhf' in download_url)): if download_url.endswith('.zip') and (
info['url'] = download_url info['arch'] in download_url or (is_arm and 'armhf' in download_url)):
break info['url'] = download_url
break
except Exception as e:
logging.error(f"Error checking remote version for {repo}: {e}")
return info return info
def make_path_for(name): def make_path_for(name):
path = os.path.join("/tmp/updates/", name) path = os.path.join("/tmp/updates/", name)
if os.path.exists(path): try:
logging.debug("[update] deleting %s" % path) if os.path.exists(path):
shutil.rmtree(path, ignore_errors=True, onerror=None) logging.debug("[update] Deleting %s" % path)
os.makedirs(path) shutil.rmtree(path, ignore_errors=True, onerror=None)
os.makedirs(path)
except Exception as e:
logging.error(f"Error creating path for {name}: {e}")
return path return path
def download_and_unzip(name, path, display, update): def download_and_unzip(name, path, display, update):
target = "%s_%s.zip" % (name, update['available']) target = f"{name}_{update['available']}.zip"
target_path = os.path.join(path, target) target_path = os.path.join(path, target)
logging.info("[update] downloading %s to %s ..." % (update['url'], target_path)) try:
display.update(force=True, new_data={'status': 'Downloading %s %s ...' % (name, update['available'])}) logging.info("[update] Downloading %s to %s ..." % (update['url'], target_path))
display.update(force=True, new_data={'status': f'Downloading {name} {update["available"]} ...'})
subprocess.run(['wget', '-q', update['url'], '-O', target_path], check=True)
os.system('wget -q "%s" -O "%s"' % (update['url'], target_path)) logging.info("[update] Extracting %s to %s ..." % (target_path, path))
display.update(force=True, new_data={'status': f'Extracting {name} {update["available"]} ...'})
subprocess.run(['unzip', target_path, '-d', path], check=True)
logging.info("[update] extracting %s to %s ..." % (target_path, path)) except Exception as e:
display.update(force=True, new_data={'status': 'Extracting %s %s ...' % (name, update['available'])}) logging.error(f"Error downloading and unzipping {name} update: {e}")
os.system('unzip "%s" -d "%s"' % (target_path, path))
def verify(name, path, source_path, display, update): def verify(name, path, source_path, display, update):
display.update(force=True, new_data={'status': 'Verifying %s %s ...' % (name, update['available'])}) display.update(force=True, new_data={'status': f'Verifying {name} {update["available"]} ...'})
checksums = glob.glob("%s/*.sha256" % path) try:
if len(checksums) == 0: checksums = glob.glob(f"{path}/*.sha256")
if update['native']: if len(checksums) == 0:
logging.warning("[update] native update without SHA256 checksum file") if update['native']:
return False logging.warning("[update] Native update without SHA256 checksum file")
return False
else:
checksum = checksums[0]
logging.info(f"[update] Verifying {checksum} for {source_path} ...")
else: with open(checksum, 'rt') as fp:
checksum = checksums[0] expected = fp.read().split('=')[1].strip().lower()
logging.info("[update] verifying %s for %s ..." % (checksum, source_path)) real = subprocess.getoutput(f'sha256sum "{source_path}"').split(' ')[0].strip().lower()
with open(checksum, 'rt') as fp: if real != expected:
expected = fp.read().split('=')[1].strip().lower() logging.warning(f"[update] Checksum mismatch for {source_path}: expected={expected} got={real}")
return False
real = subprocess.getoutput('sha256sum "%s"' % source_path).split(' ')[0].strip().lower() except Exception as e:
logging.error(f"Error verifying {name} update: {e}")
if real != expected: return False
logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (source_path, expected, real))
return False
return True return True
def install(display, update): def install(display, update):
name = update['repo'].split('/')[1] name = update['repo'].split('/')[1]
path = make_path_for(name) path = make_path_for(name)
download_and_unzip(name, path, display, update) download_and_unzip(name, path, display, update)
@ -107,37 +119,70 @@ def install(display, update):
if not verify(name, path, source_path, display, update): if not verify(name, path, source_path, display, update):
return False return False
logging.info("[update] installing %s ..." % name) try:
display.update(force=True, new_data={'status': 'Installing %s %s ...' % (name, update['available'])}) logging.info("[update] Installing %s ..." % name)
display.update(force=True, new_data={'status': f'Installing {name} {update["available"]} ...'})
if update['native']: if update['native']:
dest_path = subprocess.getoutput("which %s" % name) dest_path = subprocess.getoutput(f"which {name}")
if dest_path == "": if dest_path == "":
logging.warning("[update] can't find path for %s" % name) logging.warning(f"[update] Can't find path for {name}")
return False return False
logging.info("[update] stopping %s ..." % update['service']) logging.info(f"[update] Stopping {update['service']} ...")
os.system("service %s stop" % update['service']) subprocess.run(["service", update['service'], "stop"], check=True)
os.system("mv %s %s" % (source_path, dest_path))
logging.info("[update] restarting %s ..." % update['service'])
os.system("service %s start" % update['service'])
else:
if not os.path.exists(source_path):
source_path = "%s-%s" % (source_path, update['available'])
# setup.py is going to install data files for us subprocess.run(["mv", source_path, dest_path], check=True)
os.system("cd %s && pip3 install ." % source_path) logging.info(f"[update] Restarting {update['service']} ...")
subprocess.run(["service", update['service'], "start"], check=True)
else:
if not os.path.exists(source_path):
source_path = f"{source_path}-{update['available']}"
subprocess.run(["cd", source_path, "&&", "pip3", "install", "."], check=True, shell=True)
except Exception as e:
logging.error(f"Error installing {name} update: {e}")
return False
return True return True
def parse_version(cmd): def parse_version(cmd):
out = subprocess.getoutput(cmd) try:
for part in out.split(' '): out = subprocess.getoutput(cmd)
part = part.replace('v', '').strip() for part in out.split(' '):
if re.search(r'^\d+\.\d+\.\d+.*$', part): part = part.replace('v', '').strip()
return part if re.search(r'^\d+\.\d+\.\d+.*$', part):
raise Exception('could not parse version from "%s": output=\n%s' % (cmd, out)) return part
except Exception as e:
logging.error(f"Error parsing version from '{cmd}': {e}")
raise Exception(f'Could not parse version from "{cmd}": output=\n{out}')
def check_remote_version_with_retry(version, repo, native=True, max_retries=3):
retries = 0
while retries < max_retries:
try:
resp = requests.get(f"https://api.github.com/repos/{repo}/releases/latest")
resp.raise_for_status()
latest = resp.json()
return check_remote_version(version, repo, native)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 403:
wait_time = 2 ** retries
print(f"Rate limit exceeded. Retrying after {wait_time} seconds...")
time.sleep(wait_time)
retries += 1
else:
print(f"Error checking remote version for {repo}: {e}")
raise e
except requests.exceptions.ConnectionError as ce:
wait_time = 2 ** retries
print(f"Connection error. Retrying after {wait_time} seconds...")
time.sleep(wait_time)
retries += 1
raise Exception(f"Failed to check remote version for {repo} after {max_retries} retries.")
class AutoUpdate(plugins.Plugin): class AutoUpdate(plugins.Plugin):
@ -157,23 +202,23 @@ class AutoUpdate(plugins.Plugin):
logging.error("[update] main.plugins.auto-update.interval is not set") logging.error("[update] main.plugins.auto-update.interval is not set")
return return
self.ready = True self.ready = True
logging.info("[update] plugin loaded.") logging.info("[update] Plugin loaded.")
def on_internet_available(self, agent): def on_internet_available(self, agent):
if self.lock.locked(): if self.lock.locked():
return return
with self.lock: with self.lock:
logging.debug("[update] internet connectivity is available (ready %s)" % self.ready) logging.debug("[update] Internet connectivity is available (ready %s)" % self.ready)
if not self.ready: if not self.ready:
return return
if self.status.newer_then_hours(self.options['interval']): if self.status.newer_then_hours(self.options['interval']):
logging.debug("[update] last check happened less than %d hours ago" % self.options['interval']) logging.debug("[update] Last check happened less than %d hours ago" % self.options['interval'])
return return
logging.info("[update] checking for updates ...") logging.info("[update] Checking for updates ...")
display = agent.view() display = agent.view()
prev_status = display.get('status') prev_status = display.get('status')
@ -189,11 +234,10 @@ class AutoUpdate(plugins.Plugin):
] ]
for repo, local_version, is_native, svc_name in to_check: for repo, local_version, is_native, svc_name in to_check:
info = check(local_version, repo, is_native) info = check_remote_version_with_retry(local_version, repo, is_native)
if info['url'] is not None: if info['url'] is not None:
logging.warning( logging.warning(
"update for %s available (local version is '%s'): %s" % ( f"Update for {repo} available (local version is '{info['current']}'): {info['url']}")
repo, info['current'], info['url']))
info['service'] = svc_name info['service'] = svc_name
to_install.append(info) to_install.append(info)
@ -207,9 +251,9 @@ class AutoUpdate(plugins.Plugin):
if install(display, update): if install(display, update):
num_installed += 1 num_installed += 1
else: else:
prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '') prev_status = f"{num_updates} new update{'s' if num_updates > 1 else ''} available!"
logging.info("[update] done") logging.info("[update] Done")
self.status.update() self.status.update()

View File

@ -25,7 +25,7 @@ class GPS(plugins.Plugin):
logging.info(f"gps plugin loaded for {self.options['device']}") logging.info(f"gps plugin loaded for {self.options['device']}")
def on_ready(self, agent): def on_ready(self, agent):
if os.path.exists(self.options["device"]): if os.path.exists(self.options["device"]) or ":" in self.options["device"]:
logging.info( logging.info(
f"enabling bettercap's gps module for {self.options['device']}" f"enabling bettercap's gps module for {self.options['device']}"
) )

View File

@ -10,25 +10,30 @@ GUIDE HERE: https://community.pwnagotchi.ai/t/setting-up-paw-gps-on-android
class PawGPS(plugins.Plugin): class PawGPS(plugins.Plugin):
__author__ = 'leont' __author__ = 'leont'
__version__ = '1.0.0' __version__ = '1.0.1'
__name__ = 'pawgps' __name__ = 'pawgps'
__license__ = 'GPL3' __license__ = 'GPL3'
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android ' __description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android.'
def on_loaded(self): def on_loaded(self):
logging.info("PAW-GPS loaded") logging.info("[paw-gps] plugin loaded")
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None): if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None) or (len('ip' in self.options and self.options['ip']) is 0):
logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1:8080)") logging.info("[paw-gps] no IP Address defined in the config file, will uses paw server default (192.168.44.1:8080)")
def on_handshake(self, agent, filename, access_point, client_station): def on_handshake(self, agent, filename, access_point, client_station):
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None): if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None or (len('ip' in self.options and self.options['ip']) is 0)):
ip = "192.168.44.1:8080" ip = "192.168.44.1:8080"
else: else:
ip = self.options['ip'] ip = self.options['ip']
gps = requests.get('http://' + ip + '/gps.xhtml') try:
gps_filename = filename.replace('.pcap', '.paw-gps.json') gps = requests.get('http://' + ip + '/gps.xhtml')
try:
logging.info("saving GPS to %s (%s)" % (gps_filename, gps)) gps_filename = filename.replace('.pcap', '.paw-gps.json')
with open(gps_filename, 'w+t') as f: logging.info("[paw-gps] saving GPS data to %s" % (gps_filename))
f.write(gps.text) with open(gps_filename, 'w+t') as f:
f.write(gps.text)
except Exception as error:
logging.error(f"[paw-gps] encountered error while saving gps data: {error}")
except Exception as error:
logging.error(f"[paw-gps] encountered error while getting gps data: {error}")

View File

@ -11,6 +11,7 @@
# To display external power supply status you need to bridge the necessary pins on the UPS-Lite board. See instructions in the UPS-Lite repo. # To display external power supply status you need to bridge the necessary pins on the UPS-Lite board. See instructions in the UPS-Lite repo.
import logging import logging
import struct import struct
import subprocess
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
@ -28,11 +29,16 @@ class UPS:
import smbus import smbus
# 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
self._bus = smbus.SMBus(1) self._bus = smbus.SMBus(1)
# Version v1.1 and v1.2
self.address = 0x36
if subprocess.run(['i2cget', '-y', '1', '0x62']).returncode == 0:
# Version v1.3
self.address = 0X62
self._bus.write_word_data(self.address, 0X0A, 0x30)
def voltage(self): def voltage(self):
try: try:
address = 0x36 read = self._bus.read_word_data(self.address, 2)
read = self._bus.read_word_data(address, 2)
swapped = struct.unpack("<H", struct.pack(">H", read))[0] swapped = struct.unpack("<H", struct.pack(">H", read))[0]
return swapped * 1.25 / 1000 / 16 return swapped * 1.25 / 1000 / 16
except: except:
@ -40,8 +46,7 @@ class UPS:
def capacity(self): def capacity(self):
try: try:
address = 0x36 read = self._bus.read_word_data(self.address, 4)
read = self._bus.read_word_data(address, 4)
swapped = struct.unpack("<H", struct.pack(">H", read))[0] swapped = struct.unpack("<H", struct.pack(">H", read))[0]
return swapped / 256 return swapped / 256
except: except:
@ -60,7 +65,7 @@ class UPSLite(plugins.Plugin):
__author__ = 'evilsocket@gmail.com' __author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0' __version__ = '1.0.0'
__license__ = 'GPL3' __license__ = 'GPL3'
__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1' __description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1, v1.2, v1.3'
def __init__(self): def __init__(self):
self.ups = None self.ups = None

View File

@ -85,7 +85,6 @@ def _send_to_wigle(lines, api_key, donate=True, timeout=30):
'Accept': 'application/json'} 'Accept': 'application/json'}
data = {'donate': 'on' if donate else 'false'} data = {'donate': 'on' if donate else 'false'}
payload = {'file': dummy, 'type': 'text/csv'} payload = {'file': dummy, 'type': 'text/csv'}
try: try:
res = requests.post('https://api.wigle.net/api/v2/file/upload', res = requests.post('https://api.wigle.net/api/v2/file/upload',
data=data, data=data,
@ -141,7 +140,7 @@ class Wigle(plugins.Plugin):
all_files = os.listdir(handshake_dir) all_files = os.listdir(handshake_dir)
all_gps_files = [os.path.join(handshake_dir, filename) all_gps_files = [os.path.join(handshake_dir, filename)
for filename in all_files for filename in all_files
if filename.endswith('.gps.json' or filename.endswith('.paw-gps.json') or filename.endswith('.geo.json')] if filename.endswith('.gps.json') or filename.endswith('.paw-gps.json') or filename.endswith('.geo.json')]
all_gps_files = remove_whitelisted(all_gps_files, self.options['whitelist']) all_gps_files = remove_whitelisted(all_gps_files, self.options['whitelist'])
new_gps_files = set(all_gps_files) - set(reported) - set(self.skip) new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)

View File

@ -132,7 +132,8 @@ class WpaSec(plugins.Plugin):
cracked_file = os.path.join(handshake_dir, 'wpa-sec.cracked.potfile') cracked_file = os.path.join(handshake_dir, 'wpa-sec.cracked.potfile')
if os.path.exists(cracked_file): if os.path.exists(cracked_file):
last_check = datetime.fromtimestamp(os.path.getmtime(cracked_file)) last_check = datetime.fromtimestamp(os.path.getmtime(cracked_file))
if last_check is not None and ((datetime.now() - last_check).seconds / (60 * 60)) < 1: download_interval = int(self.options['download_interval'])
if last_check is not None and ((datetime.now() - last_check).seconds / download_interval) < 1:
return return
try: try:
self._download_from_wpasec(os.path.join(handshake_dir, 'wpa-sec.cracked.potfile')) self._download_from_wpasec(os.path.join(handshake_dir, 'wpa-sec.cracked.potfile'))

View File

@ -40,9 +40,15 @@ class Display(View):
def is_waveshare_v3(self): def is_waveshare_v3(self):
return self._implementation.name == 'waveshare_3' return self._implementation.name == 'waveshare_3'
def is_waveshare_v4(self):
return self._implementation.name == 'waveshare_4'
def is_waveshare27inch(self): def is_waveshare27inch(self):
return self._implementation.name == 'waveshare27inch' return self._implementation.name == 'waveshare27inch'
def is_waveshare27inchv2(self):
return self._implementation.name == 'waveshare27inchv2'
def is_waveshare29inch(self): def is_waveshare29inch(self):
return self._implementation.name == 'waveshare29inch' return self._implementation.name == 'waveshare29inch'
@ -67,15 +73,24 @@ class Display(View):
def is_waveshare213d(self): def is_waveshare213d(self):
return self._implementation.name == 'waveshare213d' return self._implementation.name == 'waveshare213d'
def is_waveshare213g(self):
return self._implementation.name == 'waveshare213g'
def is_waveshare213bc(self): def is_waveshare213bc(self):
return self._implementation.name == 'waveshare213bc' return self._implementation.name == 'waveshare213bc'
def is_waveshare213inb_v4(self):
return self._implementation.name == 'waveshare213inb_v4'
def is_waveshare35lcd(self): def is_waveshare35lcd(self):
return self._implementation.name == 'waveshare35lcd' return self._implementation.name == 'waveshare35lcd'
def is_spotpear24inch(self): def is_spotpear24inch(self):
return self._implementation.name == 'spotpear24inch' return self._implementation.name == 'spotpear24inch'
def is_displayhatmini(self):
return self._implementation.name == 'displayhatmini'
def is_waveshare_any(self): def is_waveshare_any(self):
return self.is_waveshare_v1() or self.is_waveshare_v2() return self.is_waveshare_v1() or self.is_waveshare_v2()

View File

@ -7,14 +7,19 @@ from pwnagotchi.ui.hw.dfrobot2 import DFRobotV2
from pwnagotchi.ui.hw.waveshare1 import WaveshareV1 from pwnagotchi.ui.hw.waveshare1 import WaveshareV1
from pwnagotchi.ui.hw.waveshare2 import WaveshareV2 from pwnagotchi.ui.hw.waveshare2 import WaveshareV2
from pwnagotchi.ui.hw.waveshare3 import WaveshareV3 from pwnagotchi.ui.hw.waveshare3 import WaveshareV3
from pwnagotchi.ui.hw.waveshare4 import WaveshareV4
from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch
from pwnagotchi.ui.hw.waveshare27inchv2 import Waveshare27inchV2
from pwnagotchi.ui.hw.waveshare29inch import Waveshare29inch from pwnagotchi.ui.hw.waveshare29inch import Waveshare29inch
from pwnagotchi.ui.hw.waveshare144lcd import Waveshare144lcd from pwnagotchi.ui.hw.waveshare144lcd import Waveshare144lcd
from pwnagotchi.ui.hw.waveshare154inch import Waveshare154inch from pwnagotchi.ui.hw.waveshare154inch import Waveshare154inch
from pwnagotchi.ui.hw.waveshare213d import Waveshare213d from pwnagotchi.ui.hw.waveshare213d import Waveshare213d
from pwnagotchi.ui.hw.waveshare213g import Waveshare213g
from pwnagotchi.ui.hw.waveshare213bc import Waveshare213bc from pwnagotchi.ui.hw.waveshare213bc import Waveshare213bc
from pwnagotchi.ui.hw.waveshare213inb_v4 import Waveshare213bV4
from pwnagotchi.ui.hw.waveshare35lcd import Waveshare35lcd from pwnagotchi.ui.hw.waveshare35lcd import Waveshare35lcd
from pwnagotchi.ui.hw.spotpear24inch import Spotpear24inch from pwnagotchi.ui.hw.spotpear24inch import Spotpear24inch
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
def display_for(config): def display_for(config):
# config has been normalized already in utils.load_config # config has been normalized already in utils.load_config
@ -45,9 +50,15 @@ def display_for(config):
elif config['ui']['display']['type'] == 'waveshare_3': elif config['ui']['display']['type'] == 'waveshare_3':
return WaveshareV3(config) return WaveshareV3(config)
elif config['ui']['display']['type'] == 'waveshare_4':
return WaveshareV4(config)
elif config['ui']['display']['type'] == 'waveshare27inch': elif config['ui']['display']['type'] == 'waveshare27inch':
return Waveshare27inch(config) return Waveshare27inch(config)
elif config['ui']['display']['type'] == 'waveshare27inchv2':
return Waveshare27inchV2(config)
elif config['ui']['display']['type'] == 'waveshare29inch': elif config['ui']['display']['type'] == 'waveshare29inch':
return Waveshare29inch(config) return Waveshare29inch(config)
@ -60,11 +71,20 @@ def display_for(config):
elif config['ui']['display']['type'] == 'waveshare213d': elif config['ui']['display']['type'] == 'waveshare213d':
return Waveshare213d(config) return Waveshare213d(config)
elif config['ui']['display']['type'] == 'waveshare213g':
return Waveshare213g(config)
elif config['ui']['display']['type'] == 'waveshare213bc': elif config['ui']['display']['type'] == 'waveshare213bc':
return Waveshare213bc(config) return Waveshare213bc(config)
elif config['ui']['display']['type'] == 'waveshare213inb_v4':
return Waveshare213bV4(config)
elif config['ui']['display']['type'] == 'waveshare35lcd': elif config['ui']['display']['type'] == 'waveshare35lcd':
return Waveshare35lcd(config) return Waveshare35lcd(config)
elif config['ui']['display']['type'] == 'spotpear24inch': elif config['ui']['display']['type'] == 'spotpear24inch':
return Spotpear24inch(config) return Spotpear24inch(config)
elif config['ui']['display']['type'] == 'displayhatmini':
return DisplayHatMini(config)

View File

@ -3,6 +3,8 @@ import pwnagotchi.ui.fonts as fonts
class DisplayImpl(object): class DisplayImpl(object):
def __init__(self, config, name): def __init__(self, config, name):
if fonts.Medium is None:
fonts.init(config)
self.name = name self.name = name
self.config = config['ui']['display'] self.config = config['ui']['display']
self._layout = { self._layout = {

View File

@ -46,7 +46,7 @@ class EPD:
self.cs_pin = epdconfig.CS_PIN self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH self.width = EPD_WIDTH
self.height = EPD_HEIGHT self.height = EPD_HEIGHT
lut_partial_update= [ lut_partial_update= [
0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
@ -90,7 +90,7 @@ class EPD:
0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0, 0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
0x22,0x17,0x41,0x0,0x32,0x36, 0x22,0x17,0x41,0x0,0x32,0x36,
] ]
''' '''
function :Hardware reset function :Hardware reset
parameter: parameter:
@ -124,7 +124,7 @@ class EPD:
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
''' '''
function :Wait until the busy_pin goes LOW function :Wait until the busy_pin goes LOW
parameter: parameter:
@ -144,7 +144,7 @@ class EPD:
self.send_data(0xC7) self.send_data(0xC7)
self.send_command(0x20) # Activate Display Update Sequence self.send_command(0x20) # Activate Display Update Sequence
self.ReadBusy() self.ReadBusy()
''' '''
function : Turn On Display Part function : Turn On Display Part
parameter: parameter:
@ -154,7 +154,7 @@ class EPD:
self.send_data(0x0f) # fast:0x0c, quality:0x0f, 0xcf self.send_data(0x0f) # fast:0x0c, quality:0x0f, 0xcf
self.send_command(0x20) # Activate Display Update Sequence self.send_command(0x20) # Activate Display Update Sequence
self.ReadBusy() self.ReadBusy()
''' '''
function : Set lut function : Set lut
parameter: parameter:
@ -165,7 +165,7 @@ class EPD:
for i in range(0, 153): for i in range(0, 153):
self.send_data(lut[i]) self.send_data(lut[i])
self.ReadBusy() self.ReadBusy()
''' '''
function : Send lut data and configuration function : Send lut data and configuration
parameter: parameter:
@ -183,7 +183,7 @@ class EPD:
self.send_data(lut[157]) # VSL self.send_data(lut[157]) # VSL
self.send_command(0x2c) # VCOM self.send_command(0x2c) # VCOM
self.send_data(lut[158]) self.send_data(lut[158])
''' '''
function : Setting the display window function : Setting the display window
parameter: parameter:
@ -197,7 +197,7 @@ class EPD:
# x point must be the multiple of 8 or the last 3 bits will be ignored # x point must be the multiple of 8 or the last 3 bits will be ignored
self.send_data((x_start>>3) & 0xFF) self.send_data((x_start>>3) & 0xFF)
self.send_data((x_end>>3) & 0xFF) self.send_data((x_end>>3) & 0xFF)
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
self.send_data(y_start & 0xFF) self.send_data(y_start & 0xFF)
self.send_data((y_start >> 8) & 0xFF) self.send_data((y_start >> 8) & 0xFF)
@ -214,11 +214,11 @@ class EPD:
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
# x point must be the multiple of 8 or the last 3 bits will be ignored # x point must be the multiple of 8 or the last 3 bits will be ignored
self.send_data(x & 0xFF) self.send_data(x & 0xFF)
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
self.send_data(y & 0xFF) self.send_data(y & 0xFF)
self.send_data((y >> 8) & 0xFF) self.send_data((y >> 8) & 0xFF)
''' '''
function : Initialize the e-Paper register function : Initialize the e-Paper register
parameter: parameter:
@ -228,7 +228,7 @@ class EPD:
return -1 return -1
# EPD hardware init start # EPD hardware init start
self.reset() self.reset()
self.ReadBusy() self.ReadBusy()
self.send_command(0x12) #SWRESET self.send_command(0x12) #SWRESET
self.ReadBusy() self.ReadBusy()
@ -237,25 +237,25 @@ class EPD:
self.send_data(0xf9) self.send_data(0xf9)
self.send_data(0x00) self.send_data(0x00)
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x11) #data entry mode self.send_command(0x11) #data entry mode
self.send_data(0x03) self.send_data(0x03)
self.SetWindow(0, 0, self.width-1, self.height-1) self.SetWindow(0, 0, self.width-1, self.height-1)
self.SetCursor(0, 0) self.SetCursor(0, 0)
self.send_command(0x3c) self.send_command(0x3c)
self.send_data(0x05) self.send_data(0x05)
self.send_command(0x21) # Display update control self.send_command(0x21) # Display update control
self.send_data(0x00) self.send_data(0x00)
self.send_data(0x80) self.send_data(0x80)
self.send_command(0x18) self.send_command(0x18)
self.send_data(0x80) self.send_data(0x80)
self.ReadBusy() self.ReadBusy()
self.SetLut(self.lut_full_update) self.SetLut(self.lut_full_update)
return 0 return 0
@ -279,7 +279,7 @@ class EPD:
buf = bytearray(img.tobytes('raw')) buf = bytearray(img.tobytes('raw'))
return buf return buf
''' '''
function : Sends the image buffer in RAM to e-Paper and displays function : Sends the image buffer in RAM to e-Paper and displays
parameter: parameter:
@ -296,7 +296,7 @@ class EPD:
for i in range(0, linewidth): for i in range(0, linewidth):
self.send_data(image[i + j * linewidth]) self.send_data(image[i + j * linewidth])
self.TurnOnDisplay() self.TurnOnDisplay()
''' '''
function : Sends the image buffer in RAM to e-Paper and partial refresh function : Sends the image buffer in RAM to e-Paper and partial refresh
parameter: parameter:
@ -311,7 +311,7 @@ class EPD:
epdconfig.digital_write(self.reset_pin, 0) epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(1) epdconfig.delay_ms(1)
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
self.SetLut(self.lut_partial_update) self.SetLut(self.lut_partial_update)
self.send_command(0x37) self.send_command(0x37)
self.send_data(0x00) self.send_data(0x00)
@ -335,7 +335,7 @@ class EPD:
self.SetWindow(0, 0, self.width - 1, self.height - 1) self.SetWindow(0, 0, self.width - 1, self.height - 1)
self.SetCursor(0, 0) self.SetCursor(0, 0)
self.send_command(0x24) # WRITE_RAM self.send_command(0x24) # WRITE_RAM
for j in range(0, self.height): for j in range(0, self.height):
for i in range(0, linewidth): for i in range(0, linewidth):
@ -357,13 +357,13 @@ class EPD:
for j in range(0, self.height): for j in range(0, self.height):
for i in range(0, linewidth): for i in range(0, linewidth):
self.send_data(image[i + j * linewidth]) self.send_data(image[i + j * linewidth])
self.send_command(0x26) self.send_command(0x26)
for j in range(0, self.height): for j in range(0, self.height):
for i in range(0, linewidth): for i in range(0, linewidth):
self.send_data(image[i + j * linewidth]) self.send_data(image[i + j * linewidth])
self.TurnOnDisplay() self.TurnOnDisplay()
''' '''
function : Clear screen function : Clear screen
parameter: parameter:
@ -374,12 +374,12 @@ class EPD:
else: else:
linewidth = int(self.width/8) + 1 linewidth = int(self.width/8) + 1
# logger.debug(linewidth) # logger.debug(linewidth)
self.send_command(0x24) self.send_command(0x24)
for j in range(0, self.height): for j in range(0, self.height):
for i in range(0, linewidth): for i in range(0, linewidth):
self.send_data(color) self.send_data(color)
self.TurnOnDisplay() self.TurnOnDisplay()
''' '''
@ -389,7 +389,7 @@ class EPD:
def sleep(self): def sleep(self):
self.send_command(0x10) #enter deep sleep self.send_command(0x10) #enter deep sleep
self.send_data(0x01) self.send_data(0x01)
epdconfig.delay_ms(2000) epdconfig.delay_ms(2000)
epdconfig.module_exit() epdconfig.module_exit()

View File

@ -10,7 +10,7 @@ class WaveshareV3(DisplayImpl):
self._display = None self._display = None
def layout(self): def layout(self):
fonts.setup(10, 8, 10, 25, 25, 9) fonts.setup(10, 8, 10, 35, 25, 9)
self._layout['width'] = 250 self._layout['width'] = 250
self._layout['height'] = 122 self._layout['height'] = 122
self._layout['face'] = (0, 40) self._layout['face'] = (0, 40)

View File

@ -4,6 +4,7 @@
<head> <head>
{% block meta %} {% block meta %}
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
{% endblock %} {% endblock %}
@ -15,6 +16,8 @@
{% block styles %} {% block styles %}
<link rel="stylesheet" href="/js/jquery.mobile/jquery.mobile-1.4.5.min.css"/> <link rel="stylesheet" href="/js/jquery.mobile/jquery.mobile-1.4.5.min.css"/>
<link rel="stylesheet" type="text/css" href="/css/style.css"/> <link rel="stylesheet" type="text/css" href="/css/style.css"/>
<link rel="apple-touch-icon" href="/images/pwnagotchi.png">
<link rel="icon" type="image/png" href="/images/pwnagotchi.png">
{% endblock %} {% endblock %}
</head> </head>

View File

@ -8,54 +8,61 @@ Plugins
{% block styles %} {% block styles %}
{{ super() }} {{ super() }}
<style> <style>
.tooltip { .plugins-box {
position: relative; position: relative;
display: inline-block; display: inline-block;
} margin: 10px;
.tooltip .tooltiptext { }
visibility: hidden;
width: 200px;
background-color: #3388cc;
color: #fff;
text-align: center;
border-radius: 10px;
border: 2px solid black;
padding: 20px 0;
position: absolute; .tooltip {
z-index: 1; position: relative;
top: 100%; display: inline-block;
left: 50%; }
margin-left: -100px;
}
.tooltip:hover .tooltiptext { .tooltip .tooltiptext {
visibility: visible; visibility: hidden;
} width: 100%;
max-width: 400px;
background-color: #3388cc;
color: #fff;
text-align: center;
border-radius: 10px;
border: 2px solid black;
padding: 20px 0;
position: fixed;
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
</style> </style>
{% endblock %} {% endblock %}
{% block script %} {% block script %}
$(function(){ $(function(){
$('input[type=checkbox]').change(function(e) { $('input[type=checkbox]').change(function(e) {
var checkbox = $(this); var checkbox = $(this);
var form = checkbox.closest('form'); var form = checkbox.closest('form');
var url = form.attr('action'); var url = form.attr('action');
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: url, url: url,
data: form.serialize(), data: form.serialize(),
success: function(data) { success: function(data) {
if( data.indexOf('failed') != -1 ) { if (data.indexOf('failed') != -1) {
alert('Could not be toggled.'); alert('Could not be toggled.');
} }
} }
}); });
}); });
}); });
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="container"> <div id="container">
{% for name in database.keys() | sort %} {% for name in database.keys() | sort %}
@ -65,6 +72,11 @@ $(function(){
<h4> <h4>
<a {% if name in loaded and loaded[name].on_webhook is defined %} href="/plugins/{{name}}" {% endif %}>{{name}}</a> <a {% if name in loaded and loaded[name].on_webhook is defined %} href="/plugins/{{name}}" {% endif %}>{{name}}</a>
</h4> </h4>
{% if has_info %}
{% if loaded[name].__version__ is defined %}
<p>v{{ loaded[name].__version__ }}</p>
{% endif %}
{% endif %}
{% if has_info %} {% if has_info %}
<span class="tooltiptext">{{ loaded[name].__description__ }}</span> <span class="tooltiptext">{{ loaded[name].__description__ }}</span>
{% else %} {% else %}

View File

@ -251,9 +251,15 @@ def load_config(args):
elif config['ui']['display']['type'] in ('ws_3', 'ws3', 'waveshare_3', 'waveshare3'): elif config['ui']['display']['type'] in ('ws_3', 'ws3', 'waveshare_3', 'waveshare3'):
config['ui']['display']['type'] = 'waveshare_3' config['ui']['display']['type'] = 'waveshare_3'
elif config['ui']['display']['type'] in ('ws_4', 'ws4', 'waveshare_4', 'waveshare4'):
config['ui']['display']['type'] = 'waveshare_4'
elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare_27inch', 'waveshare27inch'): elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare_27inch', 'waveshare27inch'):
config['ui']['display']['type'] = 'waveshare27inch' config['ui']['display']['type'] = 'waveshare27inch'
elif config['ui']['display']['type'] in ('ws_27inchv2', 'ws27inchv2', 'waveshare_27inchv2', 'waveshare27inchv2'):
config['ui']['display']['type'] = 'waveshare27inchv2'
elif config['ui']['display']['type'] in ('ws_29inch', 'ws29inch', 'waveshare_29inch', 'waveshare29inch'): elif config['ui']['display']['type'] in ('ws_29inch', 'ws29inch', 'waveshare_29inch', 'waveshare29inch'):
config['ui']['display']['type'] = 'waveshare29inch' config['ui']['display']['type'] = 'waveshare29inch'
@ -275,15 +281,24 @@ def load_config(args):
elif config['ui']['display']['type'] in ('ws_213d', 'ws213d', 'waveshare_213d', 'waveshare213d'): elif config['ui']['display']['type'] in ('ws_213d', 'ws213d', 'waveshare_213d', 'waveshare213d'):
config['ui']['display']['type'] = 'waveshare213d' config['ui']['display']['type'] = 'waveshare213d'
elif config['ui']['display']['type'] in ('ws_213g', 'ws213g', 'waveshare_213g', 'waveshare213g'):
config['ui']['display']['type'] = 'waveshare213g'
elif config['ui']['display']['type'] in ('ws_213bc', 'ws213bc', 'waveshare_213bc', 'waveshare213bc'): elif config['ui']['display']['type'] in ('ws_213bc', 'ws213bc', 'waveshare_213bc', 'waveshare213bc'):
config['ui']['display']['type'] = 'waveshare213bc' config['ui']['display']['type'] = 'waveshare213bc'
elif config['ui']['display']['type'] in ('ws_213bv4', 'ws213bv4', 'waveshare_213bv4', 'waveshare213inb_v4'):
config['ui']['display']['type'] = 'waveshare213inb_v4'
elif config['ui']['display']['type'] in ('waveshare35lcd'): elif config['ui']['display']['type'] in ('waveshare35lcd'):
config['ui']['display']['type'] = 'waveshare35lcd' config['ui']['display']['type'] = 'waveshare35lcd'
elif config['ui']['display']['type'] in ('spotpear24inch'): elif config['ui']['display']['type'] in ('spotpear24inch'):
config['ui']['display']['type'] = 'spotpear24inch' config['ui']['display']['type'] = 'spotpear24inch'
elif config['ui']['display']['type'] in ('displayhatmini'):
config['ui']['display']['type'] = 'displayhatmini'
else: else:
print("unsupported display type %s" % config['ui']['display']['type']) print("unsupported display type %s" % config['ui']['display']['type'])
sys.exit(1) sys.exit(1)

View File

@ -1,27 +1,194 @@
pycryptodome==3.9.4 #
requests==2.21.0 # This file is autogenerated by pip-compile with Python 3.7
PyYAML==5.3.1 # by the following command:
scapy==2.4.3 #
gym==0.14.0 # pip-compile --output-file=requirements.txt --pip-args='--retries 50' --resolver=backtracking --strip-extras requirements.in
scipy==1.3.1 #
stable-baselines==2.7.0 --index-url https://nexus.chadwaltercummings.me/repository/www.piwheels.org/simple
tensorflow==1.13.1 --extra-index-url https://nexus.chadwaltercummings.me/repository/pypi.org/simple
tensorflow-estimator==1.14.0
tweepy==3.7.0 absl-py==2.1.0
# via
# tensorboard
# tensorflow
astor==0.8.1
# via tensorflow
atari-py==0.2.6
# via gym
certifi==2024.2.2
# via requests
charset-normalizer==3.3.2
# via requests
click==7.1.2
# via flask
cloudpickle==1.6.0
# via
# gym
# stable-baselines
cycler==0.11.0
# via matplotlib
dbus-python==1.3.2
# via -r requirements.in
file-read-backwards==2.0.0 file-read-backwards==2.0.0
numpy==1.20.2 # via -r requirements.in
inky==1.2.0 flask==1.1.4
smbus2==0.3.0 # via
Pillow==6.2.0 # -r requirements.in
spidev==3.4 # flask-cors
gast==0.2.2 # flask-wtf
flask==2.0.1 flask-cors==3.0.10
flask-cors==3.0.7 # via -r requirements.in
flask-wtf==0.14.3 flask-wtf==1.1.1
dbus-python==1.2.12 # via -r requirements.in
toml==0.10.0 fonttools==4.38.0
python-dateutil==2.8.1 # via matplotlib
gast==0.5.4
# via tensorflow
google-pasta==0.2.0
# via tensorflow
grpcio==1.62.2
# via
# tensorboard
# tensorflow
gym==0.19.0
# via
# -r requirements.in
# stable-baselines
h5py==3.8.0
# via keras-applications
idna==3.7
# via requests
importlib-metadata==6.7.0
# via
# gym
# markdown
inky==1.5.0
# via -r requirements.in
itsdangerous==1.1.0
# via
# flask
# flask-wtf
jinja2==2.11.3
# via flask
joblib==1.3.2
# via stable-baselines
keras-applications==1.0.8
# via tensorflow
keras-preprocessing==1.1.2
# via tensorflow
kiwisolver==1.4.5
# via matplotlib
markdown==3.4.4
# via tensorboard
markupsafe==2.0.1
# via
# -r requirements.in
# jinja2
# wtforms
matplotlib==3.5.3
# via stable-baselines
numpy==1.21.4
# via
# -r requirements.in
# atari-py
# gym
# h5py
# inky
# keras-applications
# keras-preprocessing
# matplotlib
# opencv-python
# pandas
# scipy
# stable-baselines
# tensorboard
# tensorflow
opencv-python==4.7.0.72
# via
# gym
# stable-baselines
packaging==24.0
# via matplotlib
pandas==1.3.5
# via stable-baselines
pillow==9.5.0
# via
# -r requirements.in
# matplotlib
protobuf==3.20.3
# via
# -r requirements.in
# tensorboard
# tensorflow
pycryptodome==3.20.0
# via -r requirements.in
pyglet==2.0.10
# via gym
pyparsing==3.1.2
# via matplotlib
python-dateutil==2.9.0.post0
# via
# -r requirements.in
# matplotlib
# pandas
pytz==2024.1
# via pandas
pyyaml==6.0.1
# via -r requirements.in
requests==2.31.0
# via -r requirements.in
scapy==2.5.0
# via -r requirements.in
scipy==1.7.3
# via stable-baselines
six==1.16.0
# via
# atari-py
# flask-cors
# google-pasta
# keras-preprocessing
# python-dateutil
# tensorboard
# tensorflow
smbus2==0.4.3
# via
# -r requirements.in
# inky
spidev==3.6
# via
# -r requirements.in
# inky
stable-baselines==2.10.2
# via -r requirements.in
tensorboard==1.13.1
# via tensorflow
tensorflow==1.13.1
# via -r requirements.in
tensorflow-estimator==1.14.0
# via tensorflow
termcolor==2.3.0
# via tensorflow
toml==0.10.2
# via -r requirements.in
typing-extensions==4.7.1
# via
# importlib-metadata
# kiwisolver
urllib3==2.0.7
# via requests
websockets==8.1 websockets==8.1
RPi.GPIO # via -r requirements.in
Werkzeug==2.0.0 werkzeug==1.0.1
jinja2==3.0.3 # via
# flask
# tensorboard
wheel==0.42.0
# via
# tensorboard
# tensorflow
wrapt==1.16.0
# via tensorflow
wtforms==3.0.1
# via flask-wtf
zipp==3.15.0
# via importlib-metadata

View File

@ -101,6 +101,10 @@ def main():
main: main:
lang: {lang} lang: {lang}
ui: ui:
font:
name: 'DejaVuSansMono'
size_offset: 0
size: 0
fps: 0.3 fps: 0.3
display: display:
enabled: false enabled: false
@ -110,9 +114,8 @@ def main():
type: {display} type: {display}
web: web:
enabled: true enabled: true
address: "0.0.0.0" address: '::'
port: 8080 port: 8080
faces: faces:
look_r: '( ⚆_⚆)' look_r: '( ⚆_⚆)'
look_l: '(☉_☉ )' look_l: '(☉_☉ )'

View File

@ -1,29 +1,31 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from setuptools import setup, find_packages from setuptools import setup, find_packages
from distutils.util import strtobool from setuptools.command.install import install
import os
import glob import glob
import shutil import logging
import os
import re import re
import shutil
import warnings
log = logging.getLogger(__name__)
def install_file(source_filename, dest_filename): def install_file(source_filename, dest_filename):
# do not overwrite network configuration if it exists already # do not overwrite network configuration if it exists already
# https://github.com/evilsocket/pwnagotchi/issues/483 # https://github.com/evilsocket/pwnagotchi/issues/483
if dest_filename.startswith('/etc/network/interfaces.d/') and os.path.exists(dest_filename): if dest_filename.startswith('/etc/network/interfaces.d/') and os.path.exists(dest_filename):
print("%s exists, skipping ..." % dest_filename) log.info(f"{dest_filename} exists, skipping ...")
return return
print("installing %s to %s ..." % (source_filename, dest_filename)) log.info(f"installing {source_filename} to {dest_filename} ...")
try: dest_folder = os.path.dirname(dest_filename)
dest_folder = os.path.dirname(dest_filename) if not os.path.isdir(dest_folder):
if not os.path.isdir(dest_folder): os.makedirs(dest_folder)
os.makedirs(dest_folder)
shutil.copyfile(source_filename, dest_filename) shutil.copyfile(source_filename, dest_filename)
except Exception as e: if dest_filename.startswith("/usr/bin/"):
print("error installing %s: %s" % (source_filename, e)) os.chmod(dest_filename, 0o755)
def install_system_files(): def install_system_files():
@ -35,31 +37,54 @@ def install_system_files():
dest_filename = source_filename.replace(data_path, '') dest_filename = source_filename.replace(data_path, '')
install_file(source_filename, dest_filename) install_file(source_filename, dest_filename)
def restart_services():
# reload systemd units # reload systemd units
os.system("systemctl daemon-reload") os.system("systemctl daemon-reload")
def installer():
install_system_files()
# for people updating https://github.com/evilsocket/pwnagotchi/pull/551/files # for people updating https://github.com/evilsocket/pwnagotchi/pull/551/files
os.system("systemctl enable fstrim.timer") os.system("systemctl enable fstrim.timer")
def version(version_file):
with open(version_file, 'rt') as vf:
version_file_content = vf.read()
version_match = re.search(r"__version__\s*=\s*[\"\']([^\"\']+)", version_file_content) class CustomInstall(install):
if version_match: def run(self):
return version_match.groups()[0] super().run()
if os.geteuid() != 0:
warnings.warn(
"Not running as root, can't install pwnagotchi system files!"
)
return
install_system_files()
restart_services()
def version(version_file):
#with open(version_file, 'rt') as vf:
#version_file_content = vf.read()
#version_match = re.search(r"__version__\s*=\s*[\"\']([^\"\']+)", version_file_content)
#if version_match:
#return version_match.groups()[0]
if "PWN_VERSION" in os.environ:
return os.environ["PWN_VERSION"]
else:
with open(version_file, 'rt') as vf:
version_file_content = vf.read()
version_match = re.search(r"__version__\s*=\s*[\"\']([^\"\']+)", version_file_content)
if version_match:
return version_match.groups()[0]
return None return None
if strtobool(os.environ.get("PWNAGOTCHI_ENABLE_INSTALLER", "1")):
installer()
with open('requirements.txt') as fp: with open('requirements.txt') as fp:
required = [line.strip() for line in fp if line.strip() != ""] required = [
line.strip()
for line in fp
if line.strip() and not line.startswith("--")
]
VERSION_FILE = 'pwnagotchi/_version.py' VERSION_FILE = 'pwnagotchi/_version.py'
pwnagotchi_version = version(VERSION_FILE) pwnagotchi_version = version(VERSION_FILE)
@ -72,8 +97,11 @@ setup(name='pwnagotchi',
url='https://pwnagotchi.ai/', url='https://pwnagotchi.ai/',
license='GPL', license='GPL',
install_requires=required, install_requires=required,
cmdclass={
"install": CustomInstall,
},
scripts=['bin/pwnagotchi'], scripts=['bin/pwnagotchi'],
package_data={'pwnagotchi': ['defaults.yml', 'pwnagotchi/defaults.yml', 'locale/*/LC_MESSAGES/*.mo']}, package_data={'pwnagotchi': ['defaults.toml', 'pwnagotchi/defaults.toml', 'locale/*/LC_MESSAGES/*.mo']},
include_package_data=True, include_package_data=True,
packages=find_packages(), packages=find_packages(),
classifiers=[ classifiers=[