mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Merge branch 'jayofelony:noai' into noai
This commit is contained in:
55
.github/workflows/publish.yml
vendored
55
.github/workflows/publish.yml
vendored
@ -1,55 +0,0 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: "Raspberry Pi 32-bit"
|
||||
id: "32bit"
|
||||
- name: "Raspberry Pi 64-bit"
|
||||
id: "64bit"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: publish/build
|
||||
|
||||
- name: Extract version from file
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(cut -d "'" -f2 < publish/build/pwnagotchi/_version.py)
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Install qemu dependencies
|
||||
run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y
|
||||
|
||||
- name: Build ${{ matrix.name }} img file
|
||||
run: cd publish/build; ls -la .; pwd; make packer; make ${{ matrix.id }}
|
||||
|
||||
- name: Change name of .img.xz to add version
|
||||
run: |
|
||||
sudo chown runner:docker "pwnagotchi-${{ matrix.id }}.img"
|
||||
mv "pwnagotchi-${{ matrix.id }}.img" "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img"
|
||||
|
||||
- name: PiShrink
|
||||
run: |
|
||||
wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
|
||||
chmod +x pishrink.sh
|
||||
sudo mv pishrink.sh /usr/local/bin
|
||||
sudo pishrink.sh -aZ "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img"
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
prerelease: false
|
||||
make_latest: true
|
||||
tag_name: v${{ env.VERSION }}
|
||||
name: Pwnagotchi v${{ env.VERSION }}
|
||||
files: pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img.xz
|
||||
generate_release_notes: true
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1 @@
|
||||
|
||||
*.pyc
|
||||
*.pyc
|
@ -1,36 +0,0 @@
|
||||
_show_complete()
|
||||
{
|
||||
local cur opts node_names all_options opt_line
|
||||
all_options="
|
||||
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --wizard --check-update --donate {plugins,google}
|
||||
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
|
||||
pwnagotchi plugins list -i --installed -h --help
|
||||
pwnagotchi plugins install -h --help
|
||||
pwnagotchi plugins uninstall -h --help
|
||||
pwnagotchi plugins enable -h --help
|
||||
pwnagotchi plugins disable -h --help
|
||||
pwnagotchi plugins update -h --help
|
||||
pwnagotchi plugins upgrade -h --help
|
||||
pwnagotchi google -h --help {login,refresh}
|
||||
pwnagotchi google login -h --help
|
||||
pwnagotchi google refresh -h --help
|
||||
"
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
# shellcheck disable=SC2124
|
||||
cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}"
|
||||
opt_line="$(grep -m1 "$cmd" <<<"$all_options")"
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
opts="$(echo "$opt_line" | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')"
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')"
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
}
|
||||
|
||||
complete -F _show_complete pwnagotchi
|
@ -1,26 +0,0 @@
|
||||
# /etc/dphys-swapfile - user settings for dphys-swapfile package
|
||||
# author Neil Franklin, last modification 2010.05.05
|
||||
# copyright ETH Zuerich Physics Departement
|
||||
# use under either modified/non-advertising BSD or GPL license
|
||||
|
||||
# this file is sourced with . so full normal sh syntax applies
|
||||
|
||||
# the default settings are added as commented out CONF_*=* lines
|
||||
|
||||
|
||||
# where we want the swapfile to be, this is the default
|
||||
#CONF_SWAPFILE=/var/swap
|
||||
|
||||
# set size to absolute value, leaving empty (default) then uses computed value
|
||||
# you most likely don't want this, unless you have an special disk situation
|
||||
CONF_SWAPSIZE=2048
|
||||
|
||||
# set size to computed value, this times RAM size, dynamically adapts,
|
||||
# guarantees that there is enough swap without wasting disk space on excess
|
||||
#CONF_SWAPFACTOR=2
|
||||
|
||||
# restrict size (computed and absolute!) to maximally this limit
|
||||
# can be set to empty for no limit, but beware of filled partitions!
|
||||
# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it
|
||||
# but is also sensible on 64bit to prevent filling /var or even / partition
|
||||
#CONF_MAXSWAP=2048
|
@ -1,6 +0,0 @@
|
||||
# /etc/modules: kernel modules to load at boot time.
|
||||
#
|
||||
# This file contains the names of kernel modules that should be loaded
|
||||
# at boot time, one per line. Lines beginning with "#" are ignored.
|
||||
# Parameters can be specified after the module name.
|
||||
i2c-dev
|
@ -1,2 +0,0 @@
|
||||
allow-hotplug eth0
|
||||
iface eth0 inet dhcp
|
@ -1,2 +0,0 @@
|
||||
auto lo usb0
|
||||
iface lo inet loopback
|
@ -1,8 +0,0 @@
|
||||
allow-hotplug usb0
|
||||
iface usb0 inet static
|
||||
address 10.0.0.2
|
||||
netmask 255.255.255.0
|
||||
network 10.0.0.0
|
||||
broadcast 10.0.0.255
|
||||
gateway 10.0.0.1
|
||||
metric 101
|
@ -1,2 +0,0 @@
|
||||
allow-hotplug wlan0
|
||||
iface wlan0 inet static
|
@ -1,13 +0,0 @@
|
||||
[Unit]
|
||||
Description=bettercap api.rest service.
|
||||
Documentation=https://bettercap.org
|
||||
Wants=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/bettercap-launcher
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -1,20 +0,0 @@
|
||||
[Unit]
|
||||
Description=Bluetooth service
|
||||
Documentation=man:bluetoothd(8)
|
||||
ConditionPathIsDirectory=/sys/class/bluetooth
|
||||
|
||||
[Service]
|
||||
Type=dbus
|
||||
BusName=org.bluez
|
||||
ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp
|
||||
NotifyAccess=main
|
||||
#WatchdogSec=10
|
||||
#Restart=on-failure
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
LimitNPROC=1
|
||||
ProtectHome=true
|
||||
ProtectSystem=full
|
||||
|
||||
[Install]
|
||||
WantedBy=bluetooth.target
|
||||
Alias=dbus-org.bluez.service
|
@ -1,19 +0,0 @@
|
||||
[Unit]
|
||||
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
|
||||
Documentation=https://pwnagotchi.org
|
||||
Wants=network.target
|
||||
After=pwngrid-peer.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=~
|
||||
ExecStart=/usr/bin/pwnagotchi-launcher
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
TasksMax=infinity
|
||||
LimitNPROC=infinity
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -1,16 +0,0 @@
|
||||
[Unit]
|
||||
Description=pwngrid peer service.
|
||||
Documentation=https://pwnagotchi.org
|
||||
Wants=network.target
|
||||
After=bettercap.service
|
||||
|
||||
[Service]
|
||||
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
|
||||
Environment=LD_LIBRARY_PATH=/usr/local/lib
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /etc/pwnagotchi/log/pwngrid-peer.log -iface wlan0mon
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -1,34 +0,0 @@
|
||||
#!/bin/sh
|
||||
_hostname=$(hostname)
|
||||
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.11/dist-packages/pwnagotchi/_version.py)
|
||||
echo
|
||||
echo "(◕‿‿◕) $_hostname"
|
||||
echo
|
||||
echo " Hi! I'm a pwnagotchi $_version, please take good care of me!"
|
||||
echo " Here are some basic things you need to know to raise me properly!"
|
||||
echo
|
||||
echo " If you want to change my configuration, use /etc/pwnagotchi/config.toml"
|
||||
echo " All plugin config files are located in /etc/pwnagotchi/conf.d/"
|
||||
echo " Read the readme if you want to use gdrivesync plugin!!"
|
||||
echo
|
||||
echo " All the configuration options can be found on /etc/pwnagotchi/default.toml,"
|
||||
echo " but don't change this file because I will recreate it every time I'm restarted!"
|
||||
echo
|
||||
echo " I use oPwnGrid as my main API, you can check stats at https://opwngrid.xyz"
|
||||
echo
|
||||
echo " I'm managed by systemd. Here are some basic commands."
|
||||
echo
|
||||
echo " If you want to know what I'm doing, you can check my logs with the command"
|
||||
echo " - pwnlog"
|
||||
echo " - sudo pwnagotchi --wizard, to help set up a config.toml"
|
||||
echo " - sudo pwnagotchi --version, to check the current version"
|
||||
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
|
||||
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
||||
echo
|
||||
echo " If you want to know if I'm running, you can use"
|
||||
echo " sudo systemctl status pwnagotchi"
|
||||
echo
|
||||
echo " You can restart me using"
|
||||
echo " pwnkill"
|
||||
echo
|
||||
echo " You can learn more about me at https://pwnagotchi.org/"
|
@ -1,15 +0,0 @@
|
||||
client_config_backend: file
|
||||
client_config_file: /root/client_secrets.json
|
||||
client_config:
|
||||
client_id: <YOUR CLIENT ID>
|
||||
client_secret: <YOUR CLIENT SECRET>
|
||||
|
||||
save_credentials: True
|
||||
save_credentials_backend: file
|
||||
save_credentials_file: /root/credentials.json
|
||||
|
||||
get_refresh_token: True
|
||||
|
||||
oauth_scope:
|
||||
- https://www.googleapis.com/auth/drive
|
||||
- https://www.googleapis.com/auth/drive.install
|
@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
|
||||
# we need to decrypt something
|
||||
if is_crypted_mode; then
|
||||
while ! is_decrypted; do
|
||||
echo "Waiting for decryption..."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
reload_brcm
|
||||
start_monitor_interface
|
||||
|
||||
if is_auto_mode_no_delete; then
|
||||
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface wlan0mon
|
||||
else
|
||||
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface wlan0mon
|
||||
fi
|
@ -1,148 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
|
||||
_HTML_FORM_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Decryption</title>
|
||||
<style>
|
||||
body {{ text-align: center; padding: 150px; }}
|
||||
h1 {{ font-size: 50px; }}
|
||||
body {{ font: 20px Helvetica, sans-serif; color: #333; }}
|
||||
article {{ display: block; text-align: center; width: 650px; margin: 0 auto;}}
|
||||
input {{
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
}}
|
||||
input[type=password] {{
|
||||
width: 75%;
|
||||
font-size: 24px;
|
||||
}}
|
||||
input[type=submit] {{
|
||||
cursor: pointer;
|
||||
width: 75%;
|
||||
}}
|
||||
input[type=submit]:hover {{
|
||||
background-color: #d9d9d9;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<h1>Decryption</h1>
|
||||
<p>Some of your files are encrypted.</p>
|
||||
<p>Please provide the decryption password.</p>
|
||||
<div>
|
||||
<form action="/set-password" method="POST">
|
||||
{password_fields}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
POST_RESPONSE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
/* Center the loader */
|
||||
#loader {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 1;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: -75px 0 0 -75px;
|
||||
border: 16px solid #f3f3f3;
|
||||
border-radius: 50%;
|
||||
border-top: 16px solid #3498db;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
#myDiv {
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
function checkPwnagotchi() {
|
||||
var target = 'http://' + document.location.hostname + ':8080/';
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', target);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200 || xhr.status == 401) {
|
||||
window.location.replace(target);
|
||||
}else{
|
||||
setTimeout(checkPwnagotchi, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
setTimeout(checkPwnagotchi, 1000);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body style="margin:0;">
|
||||
|
||||
<div id="loader"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
HTML_FORM = None
|
||||
|
||||
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML_FORM.encode())
|
||||
|
||||
def do_POST(self):
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
body = self.rfile.read(content_length)
|
||||
for mapping, password in parse_qsl(body.decode('UTF-8')):
|
||||
with open('/tmp/.pwnagotchi-secret-{}'.format(mapping), 'wt') as pwfile:
|
||||
pwfile.write(password)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(POST_RESPONSE.encode())
|
||||
|
||||
|
||||
with open('/root/.pwnagotchi-crypted') as crypted_file:
|
||||
mappings = [line.split()[0] for line in crypted_file.readlines()]
|
||||
fields = ''.join(['<label for="{m}">Passphrase for {m}:</label>\n<input type="password" id="{m}" name="{m}" value=""><br>'.format(m=m)
|
||||
for m in mappings])
|
||||
HTML_FORM = _HTML_FORM_TEMPLATE.format(password_fields=fields)
|
||||
|
||||
httpd = HTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
|
||||
httpd.serve_forever()
|
@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
sudo /usr/bin/tvservice -o
|
@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
sudo /usr/bin/tvservice -p
|
@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
start_monitor_interface
|
@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
stop_monitor_interface
|
@ -1,17 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
|
||||
# we need to decrypt something
|
||||
if is_crypted_mode; then
|
||||
while ! is_decrypted; do
|
||||
echo "Waiting for decryption..."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
if is_auto_mode; then
|
||||
/opt/.pwn/bin/pwnagotchi
|
||||
systemctl restart bettercap
|
||||
else
|
||||
/opt/.pwn/bin/pwnagotchi --manual
|
||||
fi
|
@ -1,176 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# reload mod
|
||||
reload_brcm() {
|
||||
if ! modprobe -r brcmfmac; then
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
if ! modprobe brcmfmac; then
|
||||
return 1
|
||||
fi
|
||||
sleep 2
|
||||
iw dev wlan0 set power_save off
|
||||
return 0
|
||||
}
|
||||
|
||||
# starts mon0
|
||||
start_monitor_interface() {
|
||||
ifconfig wlan0 up
|
||||
sleep 3
|
||||
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor
|
||||
sleep 2
|
||||
rfkill unblock all
|
||||
ifconfig wlan0 down
|
||||
ifconfig wlan0mon up
|
||||
iw dev wlan0mon set power_save off
|
||||
}
|
||||
|
||||
# stops mon0
|
||||
stop_monitor_interface() {
|
||||
ifconfig wlan0mon down && iw dev wlan0mon del
|
||||
reload_brcm
|
||||
ifconfig wlan0 up
|
||||
}
|
||||
|
||||
# returns 0 if the specified network interface is up
|
||||
is_interface_up() {
|
||||
if grep -qi 'up' /sys/class/net/"$1"/operstate; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# returns 0 if conditions for AUTO mode are met
|
||||
is_auto_mode() {
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-manual ]; then
|
||||
# remove the override file if found
|
||||
rm -rf /root/.pwnagotchi-manual
|
||||
return 1
|
||||
fi
|
||||
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
# remove the override file if found
|
||||
rm -rf /root/.pwnagotchi-auto
|
||||
return 0
|
||||
fi
|
||||
|
||||
# if usb0 is up, we're in MANU
|
||||
if is_interface_up usb0; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# if eth0 is up (for other boards), we're in MANU
|
||||
if is_interface_up eth0; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# no override, but none of the interfaces is up -> AUTO
|
||||
return 0
|
||||
}
|
||||
|
||||
# returns 0 if conditions for AUTO mode are met
|
||||
is_auto_mode_no_delete() {
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-manual ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# if usb0 is up, we're in MANU
|
||||
if is_interface_up usb0; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# if eth0 is up (for other boards), we're in MANU
|
||||
if is_interface_up eth0; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# no override, but none of the interfaces is up -> AUTO
|
||||
return 0
|
||||
}
|
||||
|
||||
# check if we need to decrypt something
|
||||
is_crypted_mode() {
|
||||
if [ -f /root/.pwnagotchi-crypted ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# decryption loop
|
||||
is_decrypted() {
|
||||
while read -r mapping container mount; do
|
||||
# mapping = name the device or file will be mapped to
|
||||
# container = the luks encrypted device or file
|
||||
# mount = the mountpoint
|
||||
|
||||
# fail if not mounted
|
||||
if ! mountpoint -q "$mount" >/dev/null 2>&1; then
|
||||
if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then
|
||||
</tmp/.pwnagotchi-secret-"$mapping" read -r SECRET
|
||||
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
|
||||
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/dev/null 2>&1; then
|
||||
echo "Container decrypted!"
|
||||
fi
|
||||
fi
|
||||
|
||||
if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then
|
||||
echo "Mounted /dev/mapper/$mapping to $mount"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0
|
||||
fi
|
||||
|
||||
if ! pgrep -f decryption-webserver >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 decryption-webserver &
|
||||
fi
|
||||
|
||||
if ! pgrep wpa_supplicant >/dev/null 2>&1; then
|
||||
>/tmp/wpa_supplicant.conf cat <<EOF
|
||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||
update_config=1
|
||||
ap_scan=2
|
||||
|
||||
network={
|
||||
ssid="DECRYPT-ME"
|
||||
mode=2
|
||||
key_mgmt=WPA-PSK
|
||||
psk="pwnagotchi"
|
||||
frequency=2437
|
||||
}
|
||||
EOF
|
||||
>/dev/null 2>&1 wpa_supplicant -u -s -O -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf &
|
||||
fi
|
||||
|
||||
if ! pgrep dnsmasq >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h &
|
||||
fi
|
||||
|
||||
return 1
|
||||
fi
|
||||
done </root/.pwnagotchi-crypted
|
||||
|
||||
# overwrite passwords
|
||||
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/dev/null
|
||||
# delete
|
||||
rm /tmp/.pwnagotchi-secret-*
|
||||
sync # flush
|
||||
|
||||
pkill wpa_supplicant
|
||||
pkill dnsmasq
|
||||
pid="$(pgrep -f "decryption-webserver")"
|
||||
[[ -n "$pid" ]] && kill "$pid"
|
||||
|
||||
return 0
|
||||
}
|
@ -9,7 +9,6 @@ _name = None
|
||||
config = None
|
||||
_cpu_stats = {}
|
||||
|
||||
|
||||
def set_name(new_name):
|
||||
if new_name is None:
|
||||
return
|
||||
|
@ -2,6 +2,7 @@ import logging
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.ai.epoch import Epoch
|
||||
import os
|
||||
|
||||
|
||||
# basic mood system
|
||||
@ -136,7 +137,12 @@ class Automata(object):
|
||||
self.set_grateful()
|
||||
|
||||
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
||||
|
||||
if self._epoch.blind_for % 10 == 2:
|
||||
logging.info("two blind epochs -> restarting wifi.recon...", self._epoch.blind_for)
|
||||
self.run('wifi.recon on')
|
||||
if self._epoch.blind_for and self._epoch.blind_for % 5 == 0:
|
||||
logging.info("%d epochs without visible access points -> restarting bettercap...", self._epoch.blind_for)
|
||||
os.system("systemctl restart bettercap")
|
||||
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
||||
logging.critical("%d epochs without visible access points -> restarting ...", self._epoch.blind_for)
|
||||
self._restart()
|
||||
|
2
bin/pwnagotchi → pwnagotchi/cli.py
Executable file → Normal file
2
bin/pwnagotchi → pwnagotchi/cli.py
Executable file → Normal file
@ -1,4 +1,3 @@
|
||||
#!/usr/bin/python3
|
||||
import logging
|
||||
import argparse
|
||||
import time
|
||||
@ -14,7 +13,6 @@ from pwnagotchi import utils
|
||||
from pwnagotchi.google import cmd as google_cmd
|
||||
from pwnagotchi.plugins import cmd as plugins_cmd
|
||||
from pwnagotchi import log
|
||||
from pwnagotchi import restart
|
||||
from pwnagotchi import fs
|
||||
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
|
||||
|
Binary file not shown.
@ -174,7 +174,7 @@ class BTNap:
|
||||
"""
|
||||
Wait for device
|
||||
|
||||
returns device if found None if not
|
||||
returns device if found, None if not
|
||||
"""
|
||||
logging.debug("BT-TETHER: Waiting for device")
|
||||
|
||||
|
233
pwnagotchi/plugins/default/gps_listener.py
Normal file
233
pwnagotchi/plugins/default/gps_listener.py
Normal file
@ -0,0 +1,233 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.components import LabeledValue
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
|
||||
"""
|
||||
# Android
|
||||
# Termux:API : https://f-droid.org/en/packages/com.termux.api/
|
||||
# Termux : https://f-droid.org/en/packages/com.termux/
|
||||
pkg install termux-api socat bc
|
||||
|
||||
-----
|
||||
#!/data/data/com.termux/files/usr/bin/bash
|
||||
|
||||
# Server details
|
||||
SERVER_IP="192.168.44.45" # IP of the socat receiver
|
||||
SERVER_PORT="5000" # UDP port to send data to
|
||||
|
||||
# Function to calculate checksum
|
||||
calculate_checksum() {
|
||||
local sentence="$1"
|
||||
local checksum=0
|
||||
# Loop through each character in the sentence
|
||||
for ((i = 0; i < ${#sentence}; i++)); do
|
||||
checksum=$((checksum ^ $(printf '%d' "'${sentence:i:1}")))
|
||||
done
|
||||
# Return checksum in hexadecimal
|
||||
printf "%02X" $checksum
|
||||
}
|
||||
|
||||
# Infinite loop to send GPS data
|
||||
while true; do
|
||||
# Get location data
|
||||
LOCATION=$(termux-location -p gps)
|
||||
|
||||
# Extract latitude, longitude, altitude, speed, and bearing
|
||||
LATITUDE=$(echo "$LOCATION" | jq '.latitude')
|
||||
LONGITUDE=$(echo "$LOCATION" | jq '.longitude')
|
||||
ALTITUDE=$(echo "$LOCATION" | jq '.altitude')
|
||||
SPEED=$(echo "$LOCATION" | jq '.speed') # Speed in meters per second
|
||||
BEARING=$(echo "$LOCATION" | jq '.bearing')
|
||||
|
||||
# Convert speed from meters per second to knots and km/h
|
||||
SPEED_KNOTS=$(echo "$SPEED" | awk '{printf "%.1f", $1 * 1.943844}')
|
||||
SPEED_KMH=$(echo "$SPEED" | awk '{printf "%.1f", $1 * 3.6}')
|
||||
|
||||
# Format latitude and longitude for NMEA
|
||||
LAT_DEGREES=$(printf "%.0f" "${LATITUDE%.*}")
|
||||
LAT_MINUTES=$(echo "(${LATITUDE#${LAT_DEGREES}} * 60)" | bc -l)
|
||||
LAT_DIRECTION=$(if (( $(echo "$LATITUDE >= 0" | bc -l) )); then echo "N"; else echo "S"; fi)
|
||||
LON_DEGREES=$(printf "%.0f" "${LONGITUDE%.*}")
|
||||
LON_MINUTES=$(echo "(${LONGITUDE#${LON_DEGREES}} * 60)" | bc -l)
|
||||
LON_DIRECTION=$(if (( $(echo "$LONGITUDE >= 0" | bc -l) )); then echo "E"; else echo "W"; fi)
|
||||
|
||||
# Format the NMEA GGA sentence
|
||||
RAW_NMEA_GGA="GPGGA,123519,$(printf "%02d%07.4f" ${LAT_DEGREES#-} $LAT_MINUTES),$LAT_DIRECTION,$(printf "%03d%07.4f" ${LON_DEGREES#-} $LON_MINUTES),$LON_DIRECTION,1,08,0.9,$(printf "%.1f" $ALTITUDE),M,46.9,M,,"
|
||||
CHECKSUM=$(calculate_checksum "$RAW_NMEA_GGA")
|
||||
NMEA_GGA="\$${RAW_NMEA_GGA}*${CHECKSUM}"
|
||||
|
||||
# Format the VTG sentence
|
||||
RAW_NMEA_VTG="GPVTG,$(printf "%.1f" $BEARING),T,,M,$(printf "%.1f" $SPEED_KNOTS),N,$(printf "%.1f" $SPEED_KMH),K"
|
||||
CHECKSUM_VTG=$(calculate_checksum "$RAW_NMEA_VTG")
|
||||
NMEA_VTG="\$${RAW_NMEA_VTG}*${CHECKSUM_VTG}"
|
||||
|
||||
# Send data via UDP
|
||||
echo "$NMEA_GGA"
|
||||
echo "$NMEA_GGA" | socat - UDP:$SERVER_IP:$SERVER_PORT
|
||||
#echo "$NMEA_VTG"
|
||||
#echo "$NMEA_VTG" | socat - UDP:$SERVER_IP:$SERVER_PORT
|
||||
|
||||
sleep 1
|
||||
done
|
||||
-----
|
||||
|
||||
# Pwnagotchi
|
||||
main.plugins.gps_listener.enabled = true
|
||||
|
||||
# packages
|
||||
sudo apt-get install socat
|
||||
"""
|
||||
|
||||
class GPS(plugins.Plugin):
|
||||
__author__ = 'https://github.com/krishenriksen'
|
||||
__version__ = "1.0.0"
|
||||
__license__ = "GPL3"
|
||||
__description__ = "Receive GPS coordinates via termux-location and save whenever an handshake is captured."
|
||||
|
||||
def __init__(self):
|
||||
self.listen_ip = self.get_ip_address('bnep0')
|
||||
self.listen_port = "5000"
|
||||
self.write_virtual_serial = "/dev/ttyUSB1"
|
||||
self.read_virtual_serial = "/dev/ttyUSB0"
|
||||
self.baud_rate = "19200"
|
||||
self.socat_process = None
|
||||
self.stop_event = threading.Event()
|
||||
self.status_lock = threading.Lock()
|
||||
self.status = '-'
|
||||
self.socat_thread = threading.Thread(target=self.run_socat)
|
||||
|
||||
def get_ip_address(self, interface):
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["ip", "addr", "show", interface],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
for line in result.stdout.split('\n'):
|
||||
if 'inet ' in line:
|
||||
ip_address = line.strip().split()[1].split('/')[0]
|
||||
return ip_address
|
||||
except subprocess.CalledProcessError:
|
||||
logging.warning(f"Could not get IP address for interface {interface}")
|
||||
return None
|
||||
|
||||
def set_status(self, status):
|
||||
with self.status_lock:
|
||||
self.status = status
|
||||
|
||||
def get_status(self):
|
||||
with self.status_lock:
|
||||
return self.status
|
||||
|
||||
def on_loaded(self):
|
||||
logging.info("GPS Listener plugin loaded")
|
||||
self.cleanup_virtual_serial_ports()
|
||||
self.create_virtual_serial_ports()
|
||||
self.socat_thread.start()
|
||||
|
||||
def cleanup_virtual_serial_ports(self):
|
||||
if os.path.exists(self.write_virtual_serial):
|
||||
self.log.info(f"Removing old {self.write_virtual_serial}")
|
||||
os.remove(self.write_virtual_serial)
|
||||
|
||||
if os.path.exists(self.read_virtual_serial):
|
||||
self.log.info(f"Removing old {self.read_virtual_serial}")
|
||||
os.remove(self.read_virtual_serial)
|
||||
|
||||
def create_virtual_serial_ports(self):
|
||||
self.socat_process = subprocess.Popen(
|
||||
["socat", "-d", "-d", f"pty,link={self.write_virtual_serial},mode=777",
|
||||
f"pty,link={self.read_virtual_serial},mode=777"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
|
||||
def run_socat(self):
|
||||
while not self.stop_event.is_set():
|
||||
self.socat_process = subprocess.Popen(
|
||||
["socat", f"UDP-RECVFROM:{self.listen_port},reuseaddr,bind={self.listen_ip}", "-"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
self.set_status('C')
|
||||
|
||||
with open(self.write_virtual_serial, 'w') as serial_port:
|
||||
for line in self.socat_process.stdout:
|
||||
if self.stop_event.is_set():
|
||||
break
|
||||
serial_port.write(line)
|
||||
serial_port.flush() # Ensure the data is written immediately
|
||||
self.status = 'C'
|
||||
|
||||
self.socat_process.wait()
|
||||
if self.stop_event.is_set():
|
||||
break
|
||||
|
||||
self.set_status('-')
|
||||
|
||||
def cleanup(self):
|
||||
if self.socat_process:
|
||||
self.socat_process.terminate()
|
||||
self.socat_process.wait() # Ensure the process is reaped
|
||||
self.stop_event.set()
|
||||
self.socat_thread.join()
|
||||
self.cleanup_virtual_serial_ports()
|
||||
|
||||
def on_ready(self, agent):
|
||||
if os.path.exists(self.read_virtual_serial):
|
||||
logging.info(
|
||||
f"enabling bettercap's gps module for {self.read_virtual_serial}"
|
||||
)
|
||||
try:
|
||||
agent.run("gps off")
|
||||
except Exception:
|
||||
logging.info(f"bettercap gps module was already off")
|
||||
pass
|
||||
|
||||
agent.run(f"set gps.device {self.read_virtual_serial}")
|
||||
agent.run(f"set gps.baudrate {self.baud_rate}")
|
||||
agent.run("gps on")
|
||||
|
||||
logging.info(f"bettercap gps module enabled on {self.read_virtual_serial}")
|
||||
else:
|
||||
self.set_status('NF')
|
||||
logging.warning("no GPS detected")
|
||||
|
||||
def on_handshake(self, agent, filename, access_point, client_station):
|
||||
info = agent.session()
|
||||
coordinates = info["gps"]
|
||||
gps_filename = filename.replace(".pcap", ".gps.json")
|
||||
|
||||
if coordinates and all([
|
||||
# avoid 0.000... measurements
|
||||
coordinates["Latitude"], coordinates["Longitude"]
|
||||
]):
|
||||
self.set_status('S')
|
||||
logging.info(f"saving GPS to {gps_filename} ({coordinates})")
|
||||
with open(gps_filename, "w+t") as fp:
|
||||
json.dump(coordinates, fp)
|
||||
else:
|
||||
logging.warning("not saving GPS. Couldn't find location.")
|
||||
|
||||
def on_ui_setup(self, ui):
|
||||
with ui._lock:
|
||||
ui.add_element('gps', LabeledValue(color=BLACK, label='GPS', value='-', position=(ui.width() / 2 - 40, 0), label_font=fonts.Bold, text_font=fonts.Medium))
|
||||
|
||||
def on_unload(self, ui):
|
||||
self.cleanup()
|
||||
|
||||
with ui._lock:
|
||||
ui.remove_element('gps')
|
||||
|
||||
def on_ui_update(self, ui):
|
||||
ui.set('gps', self.get_status())
|
75
pwnagotchi/plugins/default/wittypi.py
Normal file
75
pwnagotchi/plugins/default/wittypi.py
Normal file
@ -0,0 +1,75 @@
|
||||
# Witty Pi 4 L3V7
|
||||
#
|
||||
import logging
|
||||
import pwnagotchi.plugins as plugins
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.components import LabeledValue
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
|
||||
class UPS:
|
||||
I2C_MC_ADDRESS = 0x08
|
||||
I2C_VOLTAGE_IN_I = 1
|
||||
I2C_VOLTAGE_IN_D = 2
|
||||
I2C_CURRENT_OUT_I = 5
|
||||
I2C_CURRENT_OUT_D = 6
|
||||
I2C_POWER_MODE = 7
|
||||
|
||||
def __init__(self):
|
||||
# only import when the module is loaded and enabled
|
||||
import smbus
|
||||
# 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
|
||||
self._bus = smbus.SMBus(1)
|
||||
|
||||
def voltage(self):
|
||||
try:
|
||||
i = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_VOLTAGE_IN_I)
|
||||
d = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_VOLTAGE_IN_D)
|
||||
return (i + d / 100)
|
||||
except Exception as e:
|
||||
logging.info(f"register={i} failed (exception={e})")
|
||||
return 0.0
|
||||
|
||||
def current(self):
|
||||
try:
|
||||
i = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_CURRENT_OUT_I)
|
||||
d = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_CURRENT_OUT_D)
|
||||
return (i + d / 100)
|
||||
except Exception as e:
|
||||
logging.info(f"register={i} failed (exception={e})")
|
||||
return 0.0
|
||||
|
||||
def capacity(self):
|
||||
voltage = max(3.1, min(self.voltage(), 4.2)) # Clamp voltage
|
||||
return round((voltage - 3.1) / (4.2 - 3.1) * 100)
|
||||
|
||||
def charging(self):
|
||||
try:
|
||||
dc = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_POWER_MODE)
|
||||
return '+' if dc == 0 else '-'
|
||||
except:
|
||||
return '-'
|
||||
|
||||
class WittyPi(plugins.Plugin):
|
||||
__author__ = 'https://github.com/krishenriksen'
|
||||
__version__ = '1.0.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'A plugin that will display battery info from Witty Pi 4 L3V7'
|
||||
|
||||
def __init__(self):
|
||||
self.ups = None
|
||||
|
||||
def on_loaded(self):
|
||||
self.ups = UPS()
|
||||
logging.info("wittypi plugin loaded.")
|
||||
|
||||
def on_ui_setup(self, ui):
|
||||
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%', position=(ui.width() / 2 + 15, 0), label_font=fonts.Bold, text_font=fonts.Medium))
|
||||
|
||||
def on_unload(self, ui):
|
||||
with ui._lock:
|
||||
ui.remove_element('ups')
|
||||
|
||||
def on_ui_update(self, ui):
|
||||
capacity = self.ups.capacity()
|
||||
charging = self.ups.charging()
|
||||
ui.set('ups', "%2i%s" % (capacity, charging))
|
@ -1,12 +1,12 @@
|
||||
# board GPIO:
|
||||
# Key1:
|
||||
# Key2:
|
||||
# Key3:
|
||||
# Key4:
|
||||
#
|
||||
# Key1: GPIO 16
|
||||
# Key2: GPIO 20
|
||||
# Key3: GPIO 21
|
||||
# Key4: GPIO 26
|
||||
# IR: GPIO 23
|
||||
# Touch chipset:
|
||||
# HW info: https://argon40.com/products/pod-display-2-8inch
|
||||
# HW datasheet:
|
||||
# HW MANUAL: https://cdn.shopify.com/s/files/1/0556/1660/2177/files/ARGON_POD_MANUAL.pdf?v=1668390711%0A
|
||||
|
||||
import logging
|
||||
|
||||
@ -44,6 +44,9 @@ class ArgonPod(DisplayImpl):
|
||||
|
||||
def initialize(self):
|
||||
logging.info("Initializing Argon Pod display")
|
||||
logging.info("Available pins for GPIO Buttons: 16, 20, 21, 26")
|
||||
logging.info("IR available on GPIO 23")
|
||||
logging.info("Backlight pin available on GPIO 18")
|
||||
from pwnagotchi.ui.hw.libs.argon.argonpod.ILI9341 import ILI9341
|
||||
self._display = ILI9341(0, 0, 22, 18)
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class DisplayHatMini(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(DisplayHatMini, self).__init__(config, 'displayhatmini')
|
||||
@ -29,11 +27,14 @@ class DisplayHatMini(DisplayImpl):
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 20
|
||||
}
|
||||
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing Display Hat Mini")
|
||||
logging.info("Initializing Display Hat Mini")
|
||||
logging.info("Available pins for GPIO Buttons A/B/X/Y: 5, 6, 16, 24")
|
||||
logging.info("Available pins for RGB Led: 17, 27, 22")
|
||||
logging.info("Backlight pin available on GPIO 13")
|
||||
logging.info("I2C bus available on stemma QT and Breakout Garden headers")
|
||||
from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789
|
||||
self._display = ST7789(0,1,9,13)
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
# ui.display.blcolor = "olive"
|
||||
#
|
||||
# Contrast should be between 30-50, default is 40
|
||||
# Backlight are predefined in the epd.py
|
||||
# Backlight are predefined in the lcd.py
|
||||
# Available backlight colors:
|
||||
# white, grey, maroon, red, purple, fuchsia, green,
|
||||
# lime, olive, yellow, navy, blue, teal, aqua
|
||||
@ -48,10 +48,16 @@ class GfxHat(DisplayImpl):
|
||||
def initialize(self):
|
||||
contrast = self._config['contrast'] if 'contrast' in self._config else 40
|
||||
blcolor = self._config['blcolor'] if 'blcolor' in self._config else 'OLIVE'
|
||||
logging.info("initializing Pimoroni GfxHat")
|
||||
logging.info("initializing Pimoroni GfxHat - Contrast: %d Backlight color: %s" % (contrast, blcolor))
|
||||
from pwnagotchi.ui.hw.libs.pimoroni.gfxhat.epd import EPD
|
||||
self._display = EPD(contrast=contrast)
|
||||
logging.info("Initializing Pimoroni GfxHat - Contrast: %d Backlight color: %s" % (contrast, blcolor))
|
||||
logging.info("Available config options: ui.display.contrast and ui.display.color")
|
||||
logging.info("Contrast should be between 30-50, default is 40")
|
||||
logging.info("Backlight are predefined in the lcd.py")
|
||||
logging.info("Available backlight colors:")
|
||||
logging.info("white, grey, maroon, red, purple, fuchsia, green,")
|
||||
logging.info("lime, olive, yellow, navy, blue, teal, aqua")
|
||||
logging.info("Touch control work in progress (6 touch buttons with short and long press and LED feedback)")
|
||||
from pwnagotchi.ui.hw.libs.pimoroni.gfxhat.lcd import LCD
|
||||
self._display = LCD(contrast=contrast)
|
||||
self._display.Init(color_name=blcolor)
|
||||
self._display.Clear()
|
||||
|
||||
|
@ -52,11 +52,11 @@ class I2COled(DisplayImpl):
|
||||
i2caddr = self._config['i2c_addr'] if 'i2c_addr' in self._config else 0x3C
|
||||
width = self._config['width'] if 'width' in self._config else 128
|
||||
height = self._config['height'] if 'height' in self._config else 64
|
||||
logging.info("Initializing SSD1306 based %dx%d I2C Oled Display on address 0x%X" % (width, height, i2caddr))
|
||||
logging.info("Available config options: ui.display.width, ui.display.height and ui.display.i2caddr")
|
||||
|
||||
logging.info("initializing %dx%d I2C Oled Display on address 0x%X" % (width, height, i2caddr))
|
||||
|
||||
from pwnagotchi.ui.hw.libs.i2coled.epd import EPD
|
||||
self._display = EPD(address=i2caddr, width=width, height=height)
|
||||
from pwnagotchi.ui.hw.libs.i2coled.oled import OLED
|
||||
self._display = OLED(address=i2caddr, width=width, height=height)
|
||||
self._display.Init()
|
||||
self._display.Clear()
|
||||
|
||||
|
@ -9,7 +9,7 @@ EPD_HEIGHT = 64
|
||||
# disp = SSD1306.SSD1306_96_16(96, 16, address=0x3C)
|
||||
# If you change for different resolution, you have to modify the layout in pwnagotchi/ui/hw/i2coled.py
|
||||
|
||||
class EPD(object):
|
||||
class OLED(object):
|
||||
|
||||
def __init__(self, address=0x3C, width=EPD_WIDTH, height=EPD_HEIGHT):
|
||||
self.width = width
|
@ -9,7 +9,7 @@ LED_MAP = [2, 1, 0, 5, 4, 3]
|
||||
def setup():
|
||||
"""Set up the backlight on GFX HAT."""
|
||||
global _sn3218
|
||||
import sn3218 as _sn3218
|
||||
from . import sn3218 as _sn3218
|
||||
|
||||
_sn3218.enable()
|
||||
_sn3218.enable_leds(0b111111111111111111)
|
||||
|
@ -1,55 +0,0 @@
|
||||
from . import st7567
|
||||
from . import backlight
|
||||
CONTRAST = 40
|
||||
|
||||
# Define RGB colors
|
||||
WHITE = (255, 255, 255)
|
||||
GREY = (255, 255, 255)
|
||||
MAROON = (128, 0, 0)
|
||||
RED = (255, 0, 0)
|
||||
PURPLE = (128, 0, 128)
|
||||
FUCHSIA = (255, 0, 255)
|
||||
GREEN = (0, 128, 0)
|
||||
LIME = (0, 255, 0)
|
||||
OLIVE = (128, 128, 0)
|
||||
YELLOW = (255, 255, 0)
|
||||
NAVY = (0, 0, 128)
|
||||
BLUE = (0, 0, 255)
|
||||
TEAL = (0, 128, 128)
|
||||
AQUA = (0, 255, 255)
|
||||
|
||||
# Map color names to RGB values
|
||||
color_map = {
|
||||
'WHITE': WHITE,
|
||||
'GREY' : GREY,
|
||||
'MAROON': MAROON,
|
||||
'RED': RED,
|
||||
'PURPLE': PURPLE,
|
||||
'FUCHSIA': FUCHSIA,
|
||||
'GREEN' : GREEN,
|
||||
'LIME' : LIME,
|
||||
'OLIVE' : OLIVE,
|
||||
'YELLOW' : YELLOW,
|
||||
'NAVY' : NAVY,
|
||||
'BLUE' : BLUE,
|
||||
'TEAL' : TEAL,
|
||||
'AQUA' : AQUA
|
||||
}
|
||||
|
||||
class EPD(object):
|
||||
|
||||
def __init__(self, contrast=CONTRAST, blcolor=('OLIVE')):
|
||||
self.disp = st7567.ST7567()
|
||||
self.disp.contrast(contrast)
|
||||
|
||||
def Init(self, color_name):
|
||||
self.disp.setup()
|
||||
blcolor = color_map.get(color_name.upper(), OLIVE) # Default to olive if color not found
|
||||
backlight.set_all(*blcolor)
|
||||
backlight.show()
|
||||
|
||||
def Clear(self):
|
||||
self.disp.clear()
|
||||
|
||||
def Display(self, image):
|
||||
self.disp.show(image)
|
@ -1,57 +1,55 @@
|
||||
"""Library for the GFX HAT ST7567 SPI LCD."""
|
||||
from .st7567 import ST7567
|
||||
from . import st7567
|
||||
from . import backlight
|
||||
CONTRAST = 40
|
||||
|
||||
st7567 = ST7567()
|
||||
# Define RGB colors
|
||||
WHITE = (255, 255, 255)
|
||||
GREY = (255, 255, 255)
|
||||
MAROON = (128, 0, 0)
|
||||
RED = (255, 0, 0)
|
||||
PURPLE = (128, 0, 128)
|
||||
FUCHSIA = (255, 0, 255)
|
||||
GREEN = (0, 128, 0)
|
||||
LIME = (0, 255, 0)
|
||||
OLIVE = (128, 128, 0)
|
||||
YELLOW = (255, 255, 0)
|
||||
NAVY = (0, 0, 128)
|
||||
BLUE = (0, 0, 255)
|
||||
TEAL = (0, 128, 128)
|
||||
AQUA = (0, 255, 255)
|
||||
|
||||
dimensions = st7567.dimensions
|
||||
# Map color names to RGB values
|
||||
color_map = {
|
||||
'WHITE': WHITE,
|
||||
'GREY' : GREY,
|
||||
'MAROON': MAROON,
|
||||
'RED': RED,
|
||||
'PURPLE': PURPLE,
|
||||
'FUCHSIA': FUCHSIA,
|
||||
'GREEN' : GREEN,
|
||||
'LIME' : LIME,
|
||||
'OLIVE' : OLIVE,
|
||||
'YELLOW' : YELLOW,
|
||||
'NAVY' : NAVY,
|
||||
'BLUE' : BLUE,
|
||||
'TEAL' : TEAL,
|
||||
'AQUA' : AQUA
|
||||
}
|
||||
|
||||
class LCD(object):
|
||||
|
||||
def clear():
|
||||
"""Clear GFX HAT's display buffer."""
|
||||
st7567.clear()
|
||||
def __init__(self, contrast=CONTRAST, blcolor=('OLIVE')):
|
||||
self.disp = st7567.ST7567()
|
||||
self.disp.contrast(contrast)
|
||||
|
||||
def Init(self, color_name):
|
||||
self.disp.setup()
|
||||
blcolor = color_map.get(color_name.upper(), OLIVE) # Default to olive if color not found
|
||||
backlight.set_all(*blcolor)
|
||||
backlight.show()
|
||||
|
||||
def set_pixel(x, y, value):
|
||||
"""Set a single pixel in GTX HAT's display buffer.
|
||||
def Clear(self):
|
||||
self.disp.clear()
|
||||
|
||||
:param x: X position (from 0 to 127)
|
||||
:param y: Y position (from 0 to 63)
|
||||
:param value: pixel state 1 = On, 0 = Off
|
||||
|
||||
"""
|
||||
st7567.set_pixel(x, y, value)
|
||||
|
||||
|
||||
def show():
|
||||
"""Update GFX HAT with the current buffer contents."""
|
||||
st7567.show()
|
||||
|
||||
|
||||
def contrast(value):
|
||||
"""Change GFX HAT LCD contrast."""
|
||||
st7567.contrast(value)
|
||||
|
||||
|
||||
def rotation(r=0):
|
||||
"""Set the display rotation.
|
||||
|
||||
:param r: Specify the rotation in degrees: 0, or 180
|
||||
|
||||
"""
|
||||
if r == 0:
|
||||
st7567.rotated = False
|
||||
|
||||
elif r == 180:
|
||||
st7567.rotated = True
|
||||
|
||||
else:
|
||||
raise ValueError('Rotation must be 0 or 180 degrees')
|
||||
|
||||
|
||||
def get_rotation():
|
||||
"""Get the display rotation value.
|
||||
|
||||
Returns an integer, either 0, or 180
|
||||
|
||||
"""
|
||||
return 180 if st7567.rotated else 0
|
||||
def Display(self, image):
|
||||
self.disp.show(image)
|
214
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/sn3218.py
Normal file
214
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/sn3218.py
Normal file
@ -0,0 +1,214 @@
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
__version__ = '1.2.7'
|
||||
|
||||
I2C_ADDRESS = 0x54
|
||||
CMD_ENABLE_OUTPUT = 0x00
|
||||
CMD_SET_PWM_VALUES = 0x01
|
||||
CMD_ENABLE_LEDS = 0x13
|
||||
CMD_UPDATE = 0x16
|
||||
CMD_RESET = 0x17
|
||||
|
||||
if sys.version_info < (3, ):
|
||||
SMBUS = "python-smbus"
|
||||
else:
|
||||
SMBUS = "python3-smbus"
|
||||
|
||||
# Helper function to avoid exception chaining output in python3.
|
||||
# use exec to shield newer syntax from older python
|
||||
if sys.version_info < (3, 3):
|
||||
def _raise_from_none(exc):
|
||||
raise exc
|
||||
else:
|
||||
exec("def _raise_from_none(exc): raise exc from None")
|
||||
|
||||
try:
|
||||
from smbus import SMBus
|
||||
except ImportError:
|
||||
err_string = "This library requires {smbus}\nInstall with: sudo apt install {smbus}".format(smbus=SMBUS)
|
||||
import_error = ImportError(err_string)
|
||||
_raise_from_none(import_error)
|
||||
|
||||
def i2c_bus_id():
|
||||
"""
|
||||
Returns the i2c bus ID.
|
||||
"""
|
||||
with open('/proc/cpuinfo') as cpuinfo:
|
||||
revision = [l[12:-1] for l in cpuinfo if l[:8] == "Revision"][0]
|
||||
# https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md
|
||||
return 1 if int(revision, 16) >= 4 else 0
|
||||
|
||||
|
||||
def enable():
|
||||
"""
|
||||
Enables output.
|
||||
"""
|
||||
i2c.write_i2c_block_data(I2C_ADDRESS, CMD_ENABLE_OUTPUT, [0x01])
|
||||
|
||||
|
||||
def disable():
|
||||
"""
|
||||
Disables output.
|
||||
"""
|
||||
i2c.write_i2c_block_data(I2C_ADDRESS, CMD_ENABLE_OUTPUT, [0x00])
|
||||
|
||||
|
||||
def reset():
|
||||
"""
|
||||
Resets all internal registers.
|
||||
"""
|
||||
i2c.write_i2c_block_data(I2C_ADDRESS, CMD_RESET, [0xFF])
|
||||
|
||||
|
||||
def enable_leds(enable_mask):
|
||||
"""
|
||||
Enables or disables each LED channel. The first 18 bit values are
|
||||
used to determine the state of each channel (1=on, 0=off) if fewer
|
||||
than 18 bits are provided the remaining channels are turned off.
|
||||
|
||||
Args:
|
||||
enable_mask (int): up to 18 bits of data
|
||||
Raises:
|
||||
TypeError: if enable_mask is not an integer.
|
||||
"""
|
||||
if not isinstance(enable_mask, int):
|
||||
raise TypeError("enable_mask must be an integer")
|
||||
|
||||
i2c.write_i2c_block_data(I2C_ADDRESS, CMD_ENABLE_LEDS,
|
||||
[enable_mask & 0x3F, (enable_mask >> 6) & 0x3F, (enable_mask >> 12) & 0X3F])
|
||||
i2c.write_i2c_block_data(I2C_ADDRESS, CMD_UPDATE, [0xFF])
|
||||
|
||||
|
||||
def channel_gamma(channel, gamma_table):
|
||||
"""
|
||||
Overrides the gamma table for a single channel.
|
||||
|
||||
Args:
|
||||
channel (int): channel number
|
||||
gamma_table (list): list of 256 gamma correction values
|
||||
Raises:
|
||||
TypeError: if channel is not an integer.
|
||||
ValueError: if channel is not in the range 0..17.
|
||||
TypeError: if gamma_table is not a list.
|
||||
"""
|
||||
global channel_gamma_table
|
||||
|
||||
if not isinstance(channel, int):
|
||||
raise TypeError("channel must be an integer")
|
||||
|
||||
if channel not in range(18):
|
||||
raise ValueError("channel be an integer in the range 0..17")
|
||||
|
||||
if not isinstance(gamma_table, list) or len(gamma_table) != 256:
|
||||
raise TypeError("gamma_table must be a list of 256 integers")
|
||||
|
||||
channel_gamma_table[channel] = gamma_table
|
||||
|
||||
|
||||
def output(values):
|
||||
"""
|
||||
Outputs a new set of values to the driver.
|
||||
|
||||
Args:
|
||||
values (list): channel number
|
||||
Raises:
|
||||
TypeError: if values is not a list of 18 integers.
|
||||
"""
|
||||
if not isinstance(values, list) or len(values) != 18:
|
||||
raise TypeError("values must be a list of 18 integers")
|
||||
|
||||
i2c.write_i2c_block_data(I2C_ADDRESS, CMD_SET_PWM_VALUES, [channel_gamma_table[i][values[i]] for i in range(18)])
|
||||
i2c.write_i2c_block_data(I2C_ADDRESS, CMD_UPDATE, [0xFF])
|
||||
|
||||
|
||||
def output_raw(values):
|
||||
"""
|
||||
Outputs a new set of values to the driver.
|
||||
Similar to output(), but does not use channel_gamma_table.
|
||||
|
||||
Args:
|
||||
values (list): channel number
|
||||
Raises:
|
||||
TypeError: if values is not a list of 18 integers.
|
||||
"""
|
||||
# SMBus.write_i2c_block_data does the type check, so we don't have to
|
||||
if len(values) != 18:
|
||||
raise TypeError("values must be a list of 18 integers")
|
||||
|
||||
i2c.write_i2c_block_data(I2C_ADDRESS, CMD_SET_PWM_VALUES, values)
|
||||
i2c.write_i2c_block_data(I2C_ADDRESS, CMD_UPDATE, [0xFF])
|
||||
|
||||
|
||||
try:
|
||||
i2c = SMBus(i2c_bus_id())
|
||||
except IOError as e:
|
||||
warntxt="""
|
||||
###### ###### ###### ###### ###### ###### ###### ######
|
||||
i2c initialization failed - is i2c enabled on this system?
|
||||
See https://github.com/pimoroni/sn3218/wiki/missing-i2c
|
||||
###### ###### ###### ###### ###### ###### ###### ######
|
||||
"""
|
||||
warnings.warn(warntxt)
|
||||
raise(e)
|
||||
|
||||
# generate a good default gamma table
|
||||
default_gamma_table = [int(pow(255, float(i - 1) / 255)) for i in range(256)]
|
||||
channel_gamma_table = [default_gamma_table] * 18
|
||||
|
||||
enable_leds(0b111111111111111111)
|
||||
|
||||
def test_cycles():
|
||||
print("sn3218 test cycles")
|
||||
|
||||
import time
|
||||
import math
|
||||
|
||||
# enable output
|
||||
enable()
|
||||
enable_leds(0b111111111111111111)
|
||||
|
||||
print(">> test enable mask (on/off)")
|
||||
enable_mask = 0b000000000000000000
|
||||
output([0x10] * 18)
|
||||
for i in range(10):
|
||||
enable_mask = ~enable_mask
|
||||
enable_leds(enable_mask)
|
||||
time.sleep(0.15)
|
||||
|
||||
print(">> test enable mask (odd/even)")
|
||||
enable_mask = 0b101010101010101010
|
||||
output([0x10] * 18)
|
||||
for i in range(10):
|
||||
enable_mask = ~enable_mask
|
||||
enable_leds(enable_mask)
|
||||
time.sleep(0.15)
|
||||
|
||||
print(">> test enable mask (rotate)")
|
||||
enable_mask = 0b100000100000100000
|
||||
output([0x10] * 18)
|
||||
for i in range(10):
|
||||
enable_mask = ((enable_mask & 0x01) << 18) | enable_mask >> 1
|
||||
enable_leds(enable_mask)
|
||||
time.sleep(0.15)
|
||||
|
||||
print(">> test gamma gradient")
|
||||
enable_mask = 0b111111111111111111
|
||||
enable_leds(enable_mask)
|
||||
for i in range(256):
|
||||
output([((j * (256//18)) + (i * (256//18))) % 256 for j in range(18)])
|
||||
time.sleep(0.01)
|
||||
|
||||
print(">> test gamma fade")
|
||||
enable_mask = 0b111111111111111111
|
||||
enable_leds(enable_mask)
|
||||
for i in range(512):
|
||||
output([int((math.sin(float(i)/64.0) + 1.0) * 128.0)]*18)
|
||||
time.sleep(0.01)
|
||||
|
||||
# turn everything off and disable output
|
||||
output([0 for i in range(18)])
|
||||
disable()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_cycles()
|
@ -1,6 +1,6 @@
|
||||
# board GPIO:
|
||||
# A: GPIO22
|
||||
# B: GPIO23
|
||||
# A: GPIO23
|
||||
# B: GPIO24
|
||||
#
|
||||
# HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview
|
||||
|
||||
@ -38,7 +38,10 @@ class MiniPitft(DisplayImpl):
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing Adafruit Mini Pi Tft 240x240")
|
||||
logging.info("Initializing Adafruit Mini Pi Tft 240x240")
|
||||
logging.info("Available pins for GPIO Buttons: 23, 24")
|
||||
logging.info("Backlight pin available on GPIO 22")
|
||||
logging.info("I2C bus available on stemma QT header")
|
||||
from pwnagotchi.ui.hw.libs.adafruit.minipitft.ST7789 import ST7789
|
||||
self._display = ST7789(0,0,25,22)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# board GPIO:
|
||||
# A: GPIO22
|
||||
# B: GPIO23
|
||||
# A: GPIO23
|
||||
# B: GPIO24
|
||||
#
|
||||
# HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview
|
||||
|
||||
@ -39,6 +39,9 @@ class MiniPitft2(DisplayImpl):
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing Adafruit Mini Pi Tft 135x240")
|
||||
logging.info("Available pins for GPIO Buttons: 23, 24")
|
||||
logging.info("Backlight pin available on GPIO 22")
|
||||
logging.info("I2C bus available on stemma QT header")
|
||||
from pwnagotchi.ui.hw.libs.adafruit.minipitft2.ST7789 import ST7789
|
||||
self._display = ST7789(0,0,25,22)
|
||||
|
||||
|
@ -45,6 +45,10 @@ class PirateAudio(DisplayImpl):
|
||||
|
||||
def initialize(self):
|
||||
logging.info("Initializing PirateAudio - display only")
|
||||
logging.info("Available pins for GPIO Buttons A/B/X/Y: 5, 6, 16, 20 or 24")
|
||||
logging.info("refer to the pimoroni site or pinout.xyz")
|
||||
logging.info("Backlight pin available on GPIO 13")
|
||||
logging.info("I2S for the DAC available on pins: 18, 19 and 21")
|
||||
from pwnagotchi.ui.hw.libs.pimoroni.pirateaudio.ST7789 import ST7789
|
||||
self._display = ST7789(0,1,9,13)
|
||||
|
||||
|
@ -50,6 +50,9 @@ class Pitft(DisplayImpl):
|
||||
|
||||
def initialize(self):
|
||||
logging.info("Initializing adafruit pitft 320x240 screen")
|
||||
logging.info("Available pins for GPIO Buttons on the 3,2inch: 17, 22, 23, 27")
|
||||
logging.info("Available pins for GPIO Buttons on the 2,8inch: 26, 13, 12, 6, 5")
|
||||
logging.info("Backlight pin available on GPIO 18")
|
||||
from pwnagotchi.ui.hw.libs.adafruit.pitft.ILI9341 import ILI9341
|
||||
self._display = ILI9341(0, 0, 25, 18)
|
||||
|
||||
|
@ -44,6 +44,9 @@ class TftBonnet(DisplayImpl):
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing Adafruit Tft Bonnet")
|
||||
logging.info("Available pins for GPIO Buttons Up/Down/Left/Right/Center/A/B: 17, 22, 27, 23, 4, 5, 6")
|
||||
logging.info("Backlight pin available on GPIO 26")
|
||||
logging.info("I2C bus available on stemma QT header")
|
||||
from pwnagotchi.ui.hw.libs.adafruit.tftbonnet.ST7789 import ST7789
|
||||
self._display = ST7789(0,0,25,26)
|
||||
|
||||
|
@ -49,6 +49,7 @@ class Waveshareoledlcd(DisplayImpl):
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing Waveshare OLED/LCD hat")
|
||||
logging.info("Available pins for GPIO Buttons K1/K2/K3/K4: 4, 17, 23, 24")
|
||||
from pwnagotchi.ui.hw.libs.waveshare.oled.oledlcd.ST7789 import ST7789
|
||||
self._display = ST7789(0,0,22,18)
|
||||
|
||||
|
@ -49,6 +49,7 @@ class Waveshareoledlcdvert(DisplayImpl):
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing Waveshare OLED/LCD hat vertical mode")
|
||||
logging.info("Available pins for GPIO Buttons K1/K2/K3/K4: 4, 17, 23, 24")
|
||||
from pwnagotchi.ui.hw.libs.waveshare.oled.oledlcd.ST7789vert import ST7789
|
||||
self._display = ST7789(0,0,22,18)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#import _thread
|
||||
# import _thread
|
||||
import threading
|
||||
import logging
|
||||
import random
|
||||
@ -6,7 +6,6 @@ import time
|
||||
from threading import Lock
|
||||
|
||||
from PIL import ImageDraw
|
||||
from PIL import ImageColor as colors
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.plugins as plugins
|
||||
@ -19,105 +18,28 @@ from pwnagotchi.ui.components import *
|
||||
from pwnagotchi.ui.state import State
|
||||
from pwnagotchi.voice import Voice
|
||||
|
||||
WHITE = 0x00 # white is actually black on jays image
|
||||
BLACK = 0xFF # black is actually white on jays image
|
||||
|
||||
BACKGROUND_1 = 0
|
||||
FOREGROUND_1 = 1
|
||||
|
||||
BACKGROUND_L = 0
|
||||
FOREGROUND_L = 255
|
||||
|
||||
BACKGROUND_BGR_16 = (0,0,0)
|
||||
FOREGROUND_BGR_16 = (31,63,31)
|
||||
|
||||
BACKGROUND_RGB = (0,0,0)
|
||||
FOREGROUND_RGB = (255,255,255)
|
||||
|
||||
|
||||
ROOT = None
|
||||
|
||||
|
||||
|
||||
|
||||
#1 (1-bit pixels, black and white, stored with one pixel per byte)
|
||||
|
||||
#L (8-bit pixels, grayscale)
|
||||
|
||||
#P (8-bit pixels, mapped to any other mode using a color palette)
|
||||
|
||||
#BGR;16 (5,6,5 bits, for 65k color)
|
||||
|
||||
#RGB (3x8-bit pixels, true color)
|
||||
|
||||
#RGBA (4x8-bit pixels, true color with transparency mask)
|
||||
|
||||
#CMYK (4x8-bit pixels, color separation)
|
||||
|
||||
#YCbCr (3x8-bit pixels, color video format)
|
||||
|
||||
#self.FOREGROUND is the main color
|
||||
#self.BACKGROUNDGROUND is the 2ndary color, used for background
|
||||
|
||||
WHITE = 0x00 # white is actually black on jays image
|
||||
BLACK = 0xFF # black is actually white on jays image
|
||||
|
||||
|
||||
class View(object):
|
||||
def __init__(self, config, impl, state=None):
|
||||
global ROOT, BLACK, WHITE
|
||||
|
||||
#values/code for display color mode
|
||||
|
||||
self.mode = '1' # 1 = (1-bit pixels, black and white, stored with one pixel per byte)
|
||||
if hasattr(impl, 'mode'):
|
||||
self.mode = impl.mode
|
||||
|
||||
|
||||
|
||||
match self.mode:
|
||||
case '1':
|
||||
self.BACKGROUND = BACKGROUND_1
|
||||
self.FOREGROUND = FOREGROUND_1
|
||||
# do stuff is color mode is 1 when View object is created.
|
||||
case 'L':
|
||||
self.BACKGROUND = BACKGROUND_L # black 0 to 255
|
||||
self.FOREGROUND = FOREGROUND_L
|
||||
# do stuff is color mode is L when View object is created.
|
||||
case 'P':
|
||||
pass
|
||||
# do stuff is color mode is P when View object is created.
|
||||
case 'BGR;16':
|
||||
self.BACKGROUND = BACKGROUND_BGR_16 #black tuple
|
||||
self.FOREGROUND = FOREGROUND_BGR_16 #white tuple
|
||||
case 'RGB':
|
||||
self.BACKGROUND = BACKGROUND_RGB #black tuple
|
||||
self.FOREGROUND = FOREGROUND_RGB #white tuple
|
||||
# do stuff is color mode is RGB when View object is created.
|
||||
case 'RGBA':
|
||||
# do stuff is color mode is RGBA when View object is created.
|
||||
pass
|
||||
case 'CMYK':
|
||||
# do stuff is color mode is CMYK when View object is created.
|
||||
pass
|
||||
case 'YCbCr':
|
||||
# do stuff is color mode is YCbCr when View object is created.
|
||||
pass
|
||||
case _:
|
||||
# do stuff when color mode doesnt exist for display
|
||||
self.BACKGROUND = BACKGROUND_1
|
||||
self.FOREGROUND = FOREGROUND_1
|
||||
|
||||
|
||||
|
||||
self.invert = 0
|
||||
self._black = 0xFF
|
||||
self._white = 0x00
|
||||
if 'invert' in config['ui'] and config['ui']['invert'] == True:
|
||||
logging.debug("INVERT:" + str(config['ui']['invert']))
|
||||
logging.debug("INVERT BLACK/WHITES:" + str(config['ui']['invert']))
|
||||
self.invert = 1
|
||||
tmp = self.FOREGROUND
|
||||
self.FOREGROUND = self.FOREGROUND
|
||||
self.FOREGROUND = tmp
|
||||
BLACK = 0x00
|
||||
WHITE = 0xFF
|
||||
self._black = 0x00
|
||||
self._white = 0xFF
|
||||
|
||||
# setup faces from the configuration in case the user customized them
|
||||
faces.load_from_config(config['ui']['faces'])
|
||||
|
||||
|
||||
self._agent = None
|
||||
self._render_cbs = []
|
||||
self._config = config
|
||||
@ -130,40 +52,42 @@ class View(object):
|
||||
self._width = self._layout['width']
|
||||
self._height = self._layout['height']
|
||||
self._state = State(state={
|
||||
'channel': LabeledValue(color=self.FOREGROUND, label='CH', value='00', position=self._layout['channel'],
|
||||
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'],
|
||||
label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
'aps': LabeledValue(color=self.FOREGROUND, label='APS', value='0 (00)', position=self._layout['aps'],
|
||||
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'],
|
||||
label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
|
||||
'uptime': LabeledValue(color=self.FOREGROUND, label='UP', value='00:00:00', position=self._layout['uptime'],
|
||||
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'],
|
||||
label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
|
||||
'line1': Line(self._layout['line1'], color=self.FOREGROUND),
|
||||
'line2': Line(self._layout['line2'], color=self.FOREGROUND),
|
||||
'line1': Line(self._layout['line1'], color=BLACK),
|
||||
'line2': Line(self._layout['line2'], color=BLACK),
|
||||
|
||||
'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=self.FOREGROUND, font=fonts.Huge, png=config['ui']['faces']['png']),
|
||||
'face': Text(value=faces.SLEEP,
|
||||
position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']),
|
||||
color=BLACK, font=fonts.Huge, png=config['ui']['faces']['png']),
|
||||
|
||||
# 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=self.FOREGROUND),
|
||||
'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=self.FOREGROUND),
|
||||
# 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK),
|
||||
'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=BLACK),
|
||||
|
||||
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=self.FOREGROUND, font=fonts.Bold),
|
||||
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold),
|
||||
|
||||
'status': Text(value=self._voice.default(),
|
||||
position=self._layout['status']['pos'],
|
||||
color=self.FOREGROUND,
|
||||
color=BLACK,
|
||||
font=self._layout['status']['font'],
|
||||
wrap=True,
|
||||
# the current maximum number of characters per line, assuming each character is 6 pixels wide
|
||||
max_length=self._layout['status']['max']),
|
||||
|
||||
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=self.FOREGROUND,
|
||||
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK,
|
||||
position=self._layout['shakes'], label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
'mode': Text(value='AUTO', position=self._layout['mode'],
|
||||
font=fonts.Bold, color=self.FOREGROUND),
|
||||
font=fonts.Bold, color=BLACK),
|
||||
})
|
||||
|
||||
if state:
|
||||
@ -173,8 +97,8 @@ class View(object):
|
||||
plugins.on('ui_setup', self)
|
||||
|
||||
if config['ui']['fps'] > 0.0:
|
||||
threading.Thread(target=self._refresh_handler, args=(), name="UI Handler", daemon = True).start()
|
||||
|
||||
threading.Thread(target=self._refresh_handler, args=(), name="UI Handler", daemon=True).start()
|
||||
|
||||
self._ignore_changes = ()
|
||||
else:
|
||||
logging.warning("ui.fps is 0, the display will only update for major changes")
|
||||
@ -189,7 +113,7 @@ class View(object):
|
||||
self._state.has_element(key)
|
||||
|
||||
def add_element(self, key, elem):
|
||||
if self.invert is 1 and hasattr(elem, 'color'):
|
||||
if self.invert is 1 and elem.color:
|
||||
if elem.color == 0xff:
|
||||
elem.color = 0x00
|
||||
elif elem.color == 0x00:
|
||||
@ -250,7 +174,8 @@ class View(object):
|
||||
self.set('uptime', last_session.duration)
|
||||
self.set('channel', '-')
|
||||
self.set('aps', "%d" % last_session.associated)
|
||||
self.set('shakes', '%d (%s)' % (last_session.handshakes, utils.total_unique_handshakes(self._config['bettercap']['handshakes'])))
|
||||
self.set('shakes', '%d (%s)' % (
|
||||
last_session.handshakes, utils.total_unique_handshakes(self._config['bettercap']['handshakes'])))
|
||||
self.set_closest_peer(last_session.last_peer, last_session.peers)
|
||||
self.update()
|
||||
|
||||
@ -340,7 +265,7 @@ class View(object):
|
||||
|
||||
def wait(self, secs, sleeping=True):
|
||||
was_normal = self.is_normal()
|
||||
part = secs/10.0
|
||||
part = secs / 10.0
|
||||
|
||||
for step in range(0, 10):
|
||||
# if we weren't in a normal state before going
|
||||
@ -468,13 +393,13 @@ class View(object):
|
||||
state = self._state
|
||||
changes = state.changes(ignore=self._ignore_changes)
|
||||
if force or len(changes):
|
||||
self._canvas = Image.new(self.mode, (self._width, self._height), self.BACKGROUND)
|
||||
drawer = ImageDraw.Draw(self._canvas, self.mode)
|
||||
self._canvas = Image.new('1', (self._width, self._height), self._white)
|
||||
drawer = ImageDraw.Draw(self._canvas)
|
||||
|
||||
plugins.on('ui_update', self)
|
||||
|
||||
for key, lv in state.items():
|
||||
#lv is a ui element
|
||||
# lv is a ui element
|
||||
lv.draw(self._canvas, drawer)
|
||||
|
||||
web.update_frame(self._canvas)
|
||||
|
@ -1,38 +1,18 @@
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pwnagotchi"
|
||||
dynamic = ["version"]
|
||||
dependencies = [
|
||||
"PyYAML",
|
||||
"dbus-python",
|
||||
"file-read-backwards",
|
||||
"flask",
|
||||
"flask-cors",
|
||||
"flask-wtf",
|
||||
"gast",
|
||||
"gpiozero",
|
||||
"inky",
|
||||
"numpy",
|
||||
"pycryptodome",
|
||||
"pydrive2",
|
||||
"python-dateutil",
|
||||
"requests",
|
||||
"rpi-lgpio",
|
||||
"rpi_hardware_pwm",
|
||||
"scapy",
|
||||
"setuptools",
|
||||
"shimmy",
|
||||
"smbus",
|
||||
"smbus2",
|
||||
"spidev",
|
||||
"toml",
|
||||
"tweepy",
|
||||
"websockets",
|
||||
"PyYAML", "dbus-python", "file-read-backwards", "flask", "flask-cors",
|
||||
"flask-wtf", "gast", "gpiozero", "inky", "numpy", "pycryptodome", "pydrive2", "python-dateutil",
|
||||
"requests", "rpi-lgpio", "rpi_hardware_pwm", "scapy", "setuptools", "shimmy", "smbus", "smbus2",
|
||||
"spidev", "toml", "tweepy", "websockets",
|
||||
]
|
||||
requires-python = ">=3.9"
|
||||
|
||||
requires-python = ">=3.11"
|
||||
authors = [
|
||||
{name = "Evilsocket", email = "evilsocket@gmail.com"},
|
||||
{name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"}
|
||||
@ -44,12 +24,15 @@ description = "(⌐■_■) - Deep Reinforcement Learning instrumenting betterca
|
||||
readme = "README.md"
|
||||
license = {file = "LICENSE.md"}
|
||||
classifiers = [
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||
'Environment :: Console',
|
||||
]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = {attr = "pwnagotchi.__version__"}
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://pwnagotchi.org/"
|
||||
Documentation = "https://github.com/jayofelony/pwnagotchi/wiki"
|
||||
@ -59,4 +42,4 @@ Issues = "https://github.com/jayofelony/pwnagotchi/issues"
|
||||
Download = "https://github.com/jayofelony/pwnagotchi/releases/latest"
|
||||
|
||||
[project.scripts]
|
||||
pwnagotchi_cli = "bin.pwnagotchi:pwnagotchi_cli"
|
||||
pwnagotchi = "pwnagotchi.cli:pwnagotchi_cli"
|
||||
|
@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env stork -f
|
||||
|
||||
version:parser "__version__\\s*=\\s*['\"]([\\d\\.ab]+)[\"']"
|
||||
version:file "pwnagotchi/_version.py"
|
||||
version:from_user
|
||||
|
||||
git:create_tag $VERSION
|
@ -1,25 +0,0 @@
|
||||
PyYAML
|
||||
dbus-python
|
||||
file-read-backwards
|
||||
flask
|
||||
flask-cors
|
||||
flask-wtf
|
||||
gast
|
||||
gpiozero
|
||||
inky
|
||||
numpy
|
||||
pycryptodome
|
||||
pydrive2
|
||||
python-dateutil
|
||||
requests
|
||||
rpi-lgpio
|
||||
rpi_hardware_pwm
|
||||
scapy
|
||||
setuptools
|
||||
shimmy
|
||||
smbus
|
||||
smbus2
|
||||
spidev
|
||||
toml
|
||||
tweepy
|
||||
websockets
|
@ -32,28 +32,31 @@ OUTPUT=${OUTPUT:-${UNIT_HOSTNAME}-backup-$(date +%s).tgz}
|
||||
# username to use for ssh
|
||||
UNIT_USERNAME=${UNIT_USERNAME:-pi}
|
||||
# what to backup
|
||||
FILES_TO_BACKUP="/root/brain.nn \
|
||||
/root/brain.json \
|
||||
/root/.api-report.json \
|
||||
/root/.ssh \
|
||||
/root/.bashrc \
|
||||
/root/.profile \
|
||||
/root/handshakes \
|
||||
/root/peers \
|
||||
/etc/pwnagotchi/ \
|
||||
/etc/ssh/ \
|
||||
/var/log/pwnagotchi.log \
|
||||
/var/log/pwnagotchi*.gz \
|
||||
/home/pi/.ssh \
|
||||
/home/pi/.bashrc \
|
||||
/home/pi/.profile \
|
||||
/root/.api-report.json \
|
||||
/root/.auto-update \
|
||||
/root/.bt-tether* \
|
||||
/root/.net_pos_saved \
|
||||
/root/.ohc_uploads \
|
||||
/root/.wigle_uploads \
|
||||
/root/.wpa_sec_uploads"
|
||||
FILES_TO_BACKUP="
|
||||
/boot/firmware/cmdline.txt \
|
||||
/boot/firmware/config.txt \
|
||||
/root/settings.yaml \
|
||||
/root/client_secrets.json \
|
||||
/root/.api-report.json \
|
||||
/root/.ssh \
|
||||
/root/.bashrc \
|
||||
/root/.profile \
|
||||
/root/handshakes \
|
||||
/root/peers \
|
||||
/etc/modprobe.d/g_ether.conf \
|
||||
/etc/pwnagotchi/ \
|
||||
/etc/ssh/ \
|
||||
/etc/pwnagotchi/log/pwnagotchi.log \
|
||||
/etc/pwnagotchi/log/pwnagotchi*.gz \
|
||||
/home/pi/.ssh \
|
||||
/home/pi/.bashrc \
|
||||
/home/pi/.profile \
|
||||
/root/.api-report.json \
|
||||
/root/.auto-update \
|
||||
/root/.bt-tether* \
|
||||
/root/.ohc_uploads \
|
||||
/root/.wigle_uploads \
|
||||
/root/.wpa_sec_uploads"
|
||||
|
||||
ping -c 1 "${UNIT_HOSTNAME}" > /dev/null 2>&1 || {
|
||||
echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface."
|
||||
|
@ -1,217 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import yaml
|
||||
import toml
|
||||
|
||||
sys.path.insert(0,
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
||||
'../'))
|
||||
|
||||
import pwnagotchi.ui.faces as faces
|
||||
from pwnagotchi.ui.display import Display
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class CustomDisplay(Display):
|
||||
|
||||
def __init__(self, config, state):
|
||||
self.last_image = None
|
||||
super(CustomDisplay, self).__init__(config, state)
|
||||
|
||||
def _http_serve(self):
|
||||
# do nothing
|
||||
pass
|
||||
|
||||
def _on_view_rendered(self, img):
|
||||
self.last_image = img
|
||||
|
||||
def get_image(self):
|
||||
"""
|
||||
Return the saved image
|
||||
"""
|
||||
return self.last_image
|
||||
|
||||
|
||||
class DummyPeer:
|
||||
|
||||
def __init__(self):
|
||||
self.rssi = -50
|
||||
|
||||
@staticmethod
|
||||
def name():
|
||||
return "beta"
|
||||
|
||||
@staticmethod
|
||||
def pwnd_run():
|
||||
return 50
|
||||
|
||||
@staticmethod
|
||||
def pwnd_total():
|
||||
return 100
|
||||
|
||||
@staticmethod
|
||||
def first_encounter():
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def face():
|
||||
return faces.FRIEND
|
||||
|
||||
|
||||
def append_images(images, horizontal=True, xmargin=0, ymargin=0):
|
||||
w, h = zip(*(i.size for i in images))
|
||||
|
||||
if horizontal:
|
||||
t_w = sum(w)
|
||||
t_h = max(h)
|
||||
else:
|
||||
t_w = max(w)
|
||||
t_h = sum(h)
|
||||
|
||||
result = Image.new('RGB', (t_w, t_h))
|
||||
|
||||
x_offset = 0
|
||||
y_offset = 0
|
||||
|
||||
for im in images:
|
||||
result.paste(im, (x_offset, y_offset))
|
||||
if horizontal:
|
||||
x_offset += im.size[0] + xmargin
|
||||
else:
|
||||
y_offset += im.size[1] + ymargin
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="This program emulates\
|
||||
the pwnagotchi display")
|
||||
parser.add_argument('--displays', help="Which displays to use.", nargs="+", default=["waveshare_2"])
|
||||
parser.add_argument('--lang', help="Language to use", default="en")
|
||||
parser.add_argument('--output', help="Path to output image (PNG)", default="preview.png")
|
||||
parser.add_argument('--show-peer', dest="showpeer", help="This options will show a dummy peer", action="store_true")
|
||||
parser.add_argument('--xmargin', help="Add X-Margin", type=int, default=5)
|
||||
parser.add_argument('--ymargin', help="Add Y-Margin", type=int, default=5)
|
||||
args = parser.parse_args()
|
||||
|
||||
config_template = '''
|
||||
main:
|
||||
lang: {lang}
|
||||
ui:
|
||||
font:
|
||||
name: 'DejaVuSansMono'
|
||||
size_offset: 0
|
||||
size: 0
|
||||
fps: 0.3
|
||||
display:
|
||||
enabled: false
|
||||
rotation: 180
|
||||
color: black
|
||||
refresh: 30
|
||||
type: {display}
|
||||
web:
|
||||
enabled: true
|
||||
address: '::'
|
||||
port: 8080
|
||||
|
||||
faces:
|
||||
look_r: '( ⚆_⚆)'
|
||||
look_l: '(☉_☉ )'
|
||||
look_r_happy: '( ◕‿◕)'
|
||||
look_l_happy: '(◕‿◕ )'
|
||||
sleep: '(⇀‿‿↼)'
|
||||
sleep2: '(≖‿‿≖)'
|
||||
awake: '(◕‿‿◕)'
|
||||
bored: '(-__-)'
|
||||
intense: '(°▃▃°)'
|
||||
cool: '(⌐■_■)'
|
||||
happy: '(•‿‿•)'
|
||||
excited: '(ᵔ◡◡ᵔ)'
|
||||
grateful: '(^‿‿^)'
|
||||
motivated: '(☼‿‿☼)'
|
||||
demotivated: '(≖__≖)'
|
||||
smart: '(✜‿‿✜)'
|
||||
lonely: '(ب__ب)'
|
||||
sad: '(╥☁╥ )'
|
||||
friend: '(♥‿‿♥)'
|
||||
broken: '(☓‿‿☓)'
|
||||
debug: '(#__#)'
|
||||
'''
|
||||
|
||||
list_of_displays = list()
|
||||
for display_type in args.displays:
|
||||
config = yaml.safe_load(config_template.format(display=display_type,
|
||||
lang=args.lang))
|
||||
display = CustomDisplay(config=config, state={'name': f"{display_type}>"})
|
||||
list_of_displays.append(display)
|
||||
|
||||
columns = list()
|
||||
|
||||
for display in list_of_displays:
|
||||
emotions = list()
|
||||
if args.showpeer:
|
||||
display.set_closest_peer(DummyPeer(), 10)
|
||||
display.on_starting()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_ai_ready()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_normal()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_new_peer(DummyPeer())
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_lost_peer(DummyPeer())
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_free_channel('6')
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.wait(2)
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_bored()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_sad()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_motivated(1)
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_demotivated(-1)
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_excited()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_deauth({'mac': 'DE:AD:BE:EF:CA:FE'})
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_miss('test')
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_lonely()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_handshakes(1)
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_rebooting()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
|
||||
# append them all together (vertical)
|
||||
columns.append(append_images(emotions, horizontal=False, xmargin=args.xmargin, ymargin=args.ymargin))
|
||||
|
||||
# append columns side by side
|
||||
final_image = append_images(columns, horizontal=True, xmargin=args.xmargin, ymargin=args.ymargin)
|
||||
final_image.save(args.output, 'PNG')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
SystemExit(main())
|
@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -rf build dist pwnagotchi.egg-info &&
|
||||
python3 setup.py sdist bdist_wheel &&
|
||||
clear &&
|
||||
twine upload dist/*
|
110
setup.py
110
setup.py
@ -1,110 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools.command.install import install
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import warnings
|
||||
import platform
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def install_file(source_filename, dest_filename):
|
||||
# do not overwrite network configuration if it exists already
|
||||
# https://github.com/evilsocket/pwnagotchi/issues/483
|
||||
if dest_filename.startswith('/etc/network/interfaces.d/') and os.path.exists(dest_filename):
|
||||
log.info(f"{dest_filename} exists, skipping ...")
|
||||
return
|
||||
elif dest_filename.startswith('/root/') and os.path.exists(dest_filename):
|
||||
log.info(f"{dest_filename} exists, skipping ...")
|
||||
return
|
||||
|
||||
log.info(f"installing {source_filename} to {dest_filename} ...")
|
||||
dest_folder = os.path.dirname(dest_filename)
|
||||
if not os.path.isdir(dest_folder):
|
||||
os.makedirs(dest_folder)
|
||||
|
||||
shutil.copy2(source_filename, dest_filename)
|
||||
if dest_filename.startswith("/usr/bin/"):
|
||||
os.chmod(dest_filename, 0o755)
|
||||
|
||||
|
||||
def install_system_files():
|
||||
data_path = None
|
||||
if os.stat("apt_packages.txt").st_size != 0:
|
||||
f = open("apt_packages.txt", "r")
|
||||
for x in f:
|
||||
os.system(f"apt-get install -y {x}")
|
||||
f.close()
|
||||
setup_path = os.path.dirname(__file__)
|
||||
data_path = os.path.join(setup_path, "builder/data")
|
||||
|
||||
for source_filename in glob.glob("%s/**" % data_path, recursive=True):
|
||||
if os.path.isfile(source_filename):
|
||||
dest_filename = source_filename.replace(data_path, '')
|
||||
install_file(source_filename, dest_filename)
|
||||
|
||||
|
||||
def restart_services():
|
||||
# reload systemd units
|
||||
os.system("systemctl daemon-reload")
|
||||
|
||||
# for people updating https://github.com/evilsocket/pwnagotchi/pull/551/files
|
||||
os.system("systemctl enable fstrim.timer")
|
||||
|
||||
|
||||
class CustomInstall(install):
|
||||
def run(self):
|
||||
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]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
with open('requirements.txt') as fp:
|
||||
required = [
|
||||
line.strip()
|
||||
for line in fp
|
||||
if line.strip() and not line.startswith("--")
|
||||
]
|
||||
|
||||
VERSION_FILE = 'pwnagotchi/_version.py'
|
||||
pwnagotchi_version = version(VERSION_FILE)
|
||||
|
||||
setup(name='pwnagotchi',
|
||||
version=pwnagotchi_version,
|
||||
description='(⌐■_■) - Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.',
|
||||
author='evilsocket && the dev team',
|
||||
author_email='evilsocket@gmail.com',
|
||||
url='https://pwnagotchi.ai/',
|
||||
license='GPL',
|
||||
cmdclass={
|
||||
"install": CustomInstall,
|
||||
},
|
||||
scripts=['bin/pwnagotchi'],
|
||||
package_data={'pwnagotchi': ['defaults.toml', 'pwnagotchi/defaults.toml', 'locale/*/LC_MESSAGES/*.mo']},
|
||||
include_package_data=True,
|
||||
packages=find_packages(),
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||
'Environment :: Console',
|
||||
])
|
Reference in New Issue
Block a user