mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Compare commits
195 Commits
Author | SHA1 | Date | |
---|---|---|---|
ac345c2ee7 | |||
468cfd9f4f | |||
d6bc5c0e66 | |||
4905eb6b26 | |||
697a7778b1 | |||
9287283ee7 | |||
8013109ef7 | |||
f140fe1a2d | |||
8fe503c67d | |||
d81013412e | |||
3c701822bd | |||
2f5ddb492a | |||
9e3324221d | |||
b069b82984 | |||
cb7d965271 | |||
7abf9ff8da | |||
18fb956251 | |||
afb1d11cd8 | |||
627be80e6c | |||
c4c4d6c417 | |||
8dcae13ce9 | |||
1352e99774 | |||
2182d7c29d | |||
703c05a93b | |||
697cc5d88b | |||
4b04f9b7a5 | |||
c7b94a0707 | |||
5116bac2a7 | |||
51625e61f9 | |||
bf9a0a96c1 | |||
bd03f07aa8 | |||
3f13df8f20 | |||
ce0275f2ae | |||
b971f18f75 | |||
ea9d11d018 | |||
8a572f1b70 | |||
0c4f2a5093 | |||
7edd752664 | |||
cc550aa236 | |||
ff033d41d3 | |||
6d0a0d8d5f | |||
b33af167d4 | |||
9053762e71 | |||
26fef7dd99 | |||
5dd17291f7 | |||
bac79f3465 | |||
bb8dfe0244 | |||
1b16975031 | |||
dd2b559ebc | |||
93ba2a7f79 | |||
dd18072002 | |||
1eafbb841f | |||
aa817288ea | |||
43285eb2c0 | |||
4b29476c2f | |||
1a8ff930d9 | |||
8a65afdc8f | |||
6fcd1b0e23 | |||
e0b0f5d800 | |||
2243079bf6 | |||
e10eb31ed1 | |||
1c8114e444 | |||
91638a151f | |||
d4adaabcbd | |||
006cdb0fe3 | |||
29386fb945 | |||
4b4646d604 | |||
3fae6ec312 | |||
09a82aa0b4 | |||
541865a2eb | |||
928de2769d | |||
6987840da2 | |||
58de15ce2d | |||
b5ea3da619 | |||
6c68d4608f | |||
14a727954b | |||
aeada2ee6e | |||
ebb8fef3fc | |||
e531288369 | |||
2015b56c5d | |||
2497475057 | |||
0bdbbc23fd | |||
111787544f | |||
efa5d8b197 | |||
9e5fb49d7c | |||
cbbd7d5a6d | |||
90c5818123 | |||
eb76ef4c54 | |||
59e42daeb5 | |||
5b7014c68e | |||
42cc3136ee | |||
2a243870f7 | |||
8f043ff5ef | |||
255bbdbc08 | |||
6f57b66cf4 | |||
a4c25e9996 | |||
9495d55296 | |||
69e7503d67 | |||
25cb1f2175 | |||
54b7ce5d0d | |||
1f9afd541d | |||
87eae76a58 | |||
6691257036 | |||
2149c5dbdf | |||
77af772b4b | |||
14e4fc6d47 | |||
5be8580a59 | |||
f7a599ab8f | |||
5f907b236a | |||
bc92613700 | |||
501ec9ca2b | |||
e5e0180f3c | |||
ea60808700 | |||
a34db250b5 | |||
d29aca15a9 | |||
8531b89771 | |||
59d510d0e1 | |||
913b1a6e1d | |||
de2cdaa3c9 | |||
f2cf34a8b9 | |||
bbb46128fe | |||
46713b6e73 | |||
aa2b09fb21 | |||
9125e43b20 | |||
7e4d926b14 | |||
6417ef5a78 | |||
46c03063fe | |||
d5384d5a81 | |||
e800c66e57 | |||
e3a404cb39 | |||
6cb6aaeb81 | |||
5761dac073 | |||
3ada0628e1 | |||
9fa772c36a | |||
a92e66137c | |||
a7e98cf166 | |||
9a1a264a9f | |||
8a0d482fe0 | |||
0cc320d31f | |||
d841d2b649 | |||
966126a986 | |||
d67935fa6a | |||
46d867ce7f | |||
2e16069850 | |||
6e3cbbdd39 | |||
a19bd6a181 | |||
6c03d95724 | |||
60a9da9acc | |||
25aba0d73c | |||
ecde496534 | |||
7ebd4dd7a0 | |||
35f1707436 | |||
60ced12ea5 | |||
6082a0c169 | |||
a72c1b1628 | |||
e37a0bdc62 | |||
262f8bf551 | |||
5311dd9ddc | |||
bd9c44b4a3 | |||
3b01aec872 | |||
3e571bff2d | |||
391941e64a | |||
3fff6182ed | |||
73e0a1fce7 | |||
9ba23d77d1 | |||
8be75627e9 | |||
c2a36aa678 | |||
fdf0a087f6 | |||
c6efa5df08 | |||
62327a711e | |||
bb460a9cc6 | |||
93b2322ab5 | |||
53a8af4711 | |||
ed5decffa0 | |||
147cbfaa07 | |||
ced9b4d5ee | |||
472c3165ed | |||
1578d13d98 | |||
0965e7eb3e | |||
b789c14f14 | |||
0855b44d59 | |||
55c6007d32 | |||
a7cf8a3383 | |||
faa48b2752 | |||
d20619340c | |||
43a07fe969 | |||
bcce22c164 | |||
0264b7a7db | |||
fba5dd0341 | |||
dd7760efdf | |||
f7cd0fb1fc | |||
0585fe75fe | |||
a7634a2b4a | |||
ca4feb895e | |||
418dbf21e3 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1,3 +1,4 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: jayofelony
|
github: jayofelony
|
||||||
|
custom: https://tikkie.me/pay/dubcto94hnskg539kar0
|
228
.github/workflows/publish.yml
vendored
228
.github/workflows/publish.yml
vendored
@ -8,205 +8,51 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
build:
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Remove unnecessary directories
|
- name: Extract version from file
|
||||||
run: |
|
id: get_version
|
||||||
sudo rm -rf /usr/share/dotnet
|
run: |
|
||||||
sudo rm -rf /opt/ghc
|
VERSION=$(cut -d "'" -f2 < pwnagotchi/_version.py)
|
||||||
sudo rm -rf /usr/local/share/boost
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
|
||||||
|
|
||||||
- name: Check disk space
|
- name: Install qemu dependencies
|
||||||
run: df -BG
|
run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Build img file
|
||||||
uses: actions/checkout@v4
|
run: ls -la .; pwd; make all
|
||||||
|
|
||||||
- name: Validate tag
|
- name: Transfer 32bit.img to docker and give permissions
|
||||||
id: tag-setter
|
run: sudo chown runner:docker "pwnagotchi-32bit.img"
|
||||||
run: |
|
|
||||||
TAG=${{ github.event.inputs.version }}
|
|
||||||
if [[ $TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
||||||
echo "Tag $TAG is valid."
|
|
||||||
echo "TAG=$TAG" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "Tag $TAG is not a valid semantic version. Aborting."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Transfer 64bit.img to docker and give permissions
|
||||||
uses: actions/setup-python@v5
|
run: sudo chown runner:docker "pwnagotchi-64bit.img"
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: PiShrink
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && sudo apt-get install -y libdbus-1-dev curl unzip gettext qemu-utils qemu qemu-user-static binfmt-support
|
wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
|
||||||
pip install -r requirements.txt
|
chmod +x pishrink.sh
|
||||||
|
sudo mv pishrink.sh /usr/local/bin
|
||||||
|
find /home/runner/work/ -type f -name "*.img" -exec sudo pishrink.sh -aZ {} \;
|
||||||
|
|
||||||
- name: Update QEMU
|
- name: Change name of 32.img.xz to add version
|
||||||
run: |
|
run: mv "pwnagotchi-32bit.img.xz" "pwnagotchi-$VERSION-32bit.img.xz"
|
||||||
sudo update-binfmts --enable qemu-aarch64
|
|
||||||
echo $(ls /usr/bin/qemu-aarch64-static)
|
|
||||||
|
|
||||||
- name: Restart binfmt-support
|
- name: Change name of 64.img.xz to add version
|
||||||
run: sudo service binfmt-support restart
|
run: mv "pwnagotchi-64bit.img.xz" "pwnagotchi-$VERSION-64bit.img.xz"
|
||||||
|
|
||||||
- name: Mount binfmt_misc
|
- name: Release
|
||||||
run: |
|
uses: softprops/action-gh-release@v2
|
||||||
if ! grep -qs '/proc/sys/fs/binfmt_misc ' /proc/mounts; then
|
with:
|
||||||
echo "Mounting binfmt_misc"
|
prerelease: false
|
||||||
sudo mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
|
make_latest: true
|
||||||
fi
|
tag_name: v${{ env.VERSION }}
|
||||||
|
name: Pwnagotchi v${{ env.VERSION }}
|
||||||
- name: Restart binfmt-support
|
files: |
|
||||||
run: sudo service binfmt-support restart
|
pwnagotchi-${{ env.VERSION }}-32bit.img.xz
|
||||||
|
pwnagotchi-${{ env.VERSION }}-64bit.img.xz
|
||||||
- name: Update Languages
|
generate_release_notes: true
|
||||||
run: make update_langs
|
|
||||||
|
|
||||||
- name: Compile Languages
|
|
||||||
run: make compile_langs
|
|
||||||
|
|
||||||
- name: Check disk space
|
|
||||||
run: df -BG
|
|
||||||
|
|
||||||
- name: Check qemu-user-static package
|
|
||||||
run: |
|
|
||||||
echo "Checking qemu-user-static package..."
|
|
||||||
dpkg -s qemu-user-static && echo "qemu-user-static is installed." || echo "qemu-user-static is NOT installed."
|
|
||||||
|
|
||||||
- name: Check binfmt-support service
|
|
||||||
run: |
|
|
||||||
echo "Checking binfmt-support service..."
|
|
||||||
service binfmt-support status && echo "binfmt-support service is running." || echo "binfmt-support service is NOT running."
|
|
||||||
|
|
||||||
- name: Check binfmt_misc filesystem
|
|
||||||
run: |
|
|
||||||
echo "Checking binfmt_misc filesystem..."
|
|
||||||
mount | grep binfmt_misc && echo "binfmt_misc is mounted." || echo "binfmt_misc is NOT mounted."
|
|
||||||
echo $(ls /proc/sys/fs/binfmt_misc | grep qemu-aarch64)
|
|
||||||
|
|
||||||
- name: Run Makefile
|
|
||||||
run: make
|
|
||||||
env:
|
|
||||||
PWN_VERSION: ${{ steps.tag-setter.outputs.TAG }}
|
|
||||||
|
|
||||||
- name: PiShrink
|
|
||||||
run: |
|
|
||||||
wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
|
|
||||||
chmod +x pishrink.sh
|
|
||||||
sudo mv pishrink.sh /usr/local/bin
|
|
||||||
find /home/runner/work/ -type f -name "*.img" -exec sudo pishrink.sh {} \;
|
|
||||||
|
|
||||||
- name: Compress .img files
|
|
||||||
run: |
|
|
||||||
find /home/runner/work/ -type f -name "*.img" -exec xz --no-warn {} \;
|
|
||||||
|
|
||||||
- name: Create tag
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const version = "${{ steps.tag-setter.outputs.TAG }}"
|
|
||||||
console.log(`Creating tag ${version}`)
|
|
||||||
await github.rest.git.createRef({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
ref: `refs/tags/${version}`,
|
|
||||||
sha: context.sha
|
|
||||||
})
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
id: create_release
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const tag = "${{ steps.tag-setter.outputs.TAG }}"
|
|
||||||
console.log(`Creating release with tag: ${tag}`)
|
|
||||||
const release = await github.rest.repos.createRelease({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
tag_name: tag,
|
|
||||||
name: tag,
|
|
||||||
draft: false,
|
|
||||||
prerelease: true,
|
|
||||||
generate_release_notes: true
|
|
||||||
})
|
|
||||||
console.log(`Created release with id: ${release.data.id}`)
|
|
||||||
return release.data.id
|
|
||||||
|
|
||||||
- name: Upload Release Asset
|
|
||||||
id: upload-release-asset
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const release_id = "${{ steps.create_release.outputs.result }}";
|
|
||||||
const asset_content_type = 'application/octet-stream';
|
|
||||||
const distDir = '/home/runner/work/';
|
|
||||||
|
|
||||||
const uploadFile = async (filePath) => {
|
|
||||||
if (fs.lstatSync(filePath).isDirectory()) {
|
|
||||||
const files = fs.readdirSync(filePath);
|
|
||||||
for (const file of files) {
|
|
||||||
await uploadFile(path.join(filePath, file));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check if the file has a .xz extension
|
|
||||||
if (path.extname(filePath) === '.xz') {
|
|
||||||
console.log(`Uploading ${filePath}...`);
|
|
||||||
|
|
||||||
const asset_name = path.basename(filePath);
|
|
||||||
const asset_size = fs.statSync(filePath).size;
|
|
||||||
const asset = fs.createReadStream(filePath);
|
|
||||||
|
|
||||||
const response = await github.rest.repos.uploadReleaseAsset({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
release_id: release_id,
|
|
||||||
name: asset_name,
|
|
||||||
data: asset,
|
|
||||||
headers: {
|
|
||||||
'content-type': asset_content_type,
|
|
||||||
'content-length': asset_size
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Uploaded ${filePath}: ${response.data.browser_download_url}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await uploadFile(distDir);
|
|
||||||
|
|
||||||
- name: Update Release
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const release_id = "${{ steps.create_release.outputs.result }}"
|
|
||||||
console.log(`Updating release with id: ${release_id}`)
|
|
||||||
github.rest.repos.updateRelease({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
release_id: release_id,
|
|
||||||
tag_name: "${{ steps.tag-setter.outputs.TAG }}",
|
|
||||||
name: "${{ steps.tag-setter.outputs.TAG }}",
|
|
||||||
draft: false,
|
|
||||||
prerelease: false
|
|
||||||
})
|
|
||||||
|
|
||||||
- name: Save environment variable
|
|
||||||
run: echo "${{ steps.tag-setter.outputs.TAG }}" > env_var.txt
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: env-var
|
|
||||||
path: env_var.txt
|
|
39
Makefile
39
Makefile
@ -25,7 +25,8 @@ ifneq (,$(UNSHARE))
|
|||||||
UNSHARE := $(UNSHARE) --uts
|
UNSHARE := $(UNSHARE) --uts
|
||||||
endif
|
endif
|
||||||
|
|
||||||
all: clean image clean
|
# sudo apt-get install qemu-user-static qemu-utils
|
||||||
|
all: clean packer image
|
||||||
|
|
||||||
update_langs:
|
update_langs:
|
||||||
@for lang in pwnagotchi/locale/*/; do\
|
@for lang in pwnagotchi/locale/*/; do\
|
||||||
@ -39,30 +40,22 @@ compile_langs:
|
|||||||
./scripts/language.sh compile $$(basename $$lang); \
|
./scripts/language.sh compile $$(basename $$lang); \
|
||||||
done
|
done
|
||||||
|
|
||||||
PACKER := ~/pwnagotchi/packer
|
packer: clean
|
||||||
PACKER_URL := https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_$(GOARCH).zip
|
curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip
|
||||||
$(PACKER):
|
unzip /tmp/packer.zip -d /tmp
|
||||||
mkdir -p $(@D)
|
sudo mv /tmp/packer /usr/bin/packer
|
||||||
curl -L "$(PACKER_URL)" -o $(PACKER).zip
|
|
||||||
unzip $(PACKER).zip -d $(@D)
|
|
||||||
rm $(PACKER).zip
|
|
||||||
chmod +x $@
|
|
||||||
|
|
||||||
SDIST := dist/pwnagotchi-$(PWN_VERSION).tar.gz
|
image: clean packer
|
||||||
$(SDIST): setup.py pwnagotchi
|
export LC_ALL=en_GB.UTF-8
|
||||||
python3 setup.py sdist
|
cd builder && sudo /usr/bin/packer init combined.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" combined.json.pkr.hcl
|
||||||
|
|
||||||
# Building the image requires packer, but don't rebuild the image just because packer updated.
|
bullseye: clean packer
|
||||||
pwnagotchi: | $(PACKER)
|
export LC_ALL=en_GB.UTF-8
|
||||||
|
cd builder && sudo /usr/bin/packer init raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi32.json.pkr.hcl
|
||||||
|
|
||||||
# If the packer or ansible files are updated, rebuild the image.
|
bookworm: clean packer
|
||||||
pwnagotchi: $(SDIST) builder/pwnagotchi.json.pkr.hcl builder/raspberrypi64.yml $(shell find builder/data -type f)
|
export LC_ALL=en_GB.UTF-8
|
||||||
|
cd builder && sudo /usr/bin/packer init raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi64.json.pkr.hcl
|
||||||
cd builder && $(PACKER) init pwnagotchi.json.pkr.hcl && sudo $(UNSHARE) $(PACKER) build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json.pkr.hcl
|
|
||||||
|
|
||||||
.PHONY: image
|
|
||||||
image: pwnagotchi
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
- rm -rf dist pwnagotchi.egg-info
|
- rm -rf /tmp/packer*
|
||||||
- rm -f $(PACKER)
|
|
||||||
|
@ -11,7 +11,7 @@ Select ‘Credentials’ from the left menu, click ‘Create Credentials’, sel
|
|||||||
|
|
||||||
Now, the product name and consent screen need to be set -> click ‘Configure consent screen’ and follow the instructions. Once finished:
|
Now, the product name and consent screen need to be set -> click ‘Configure consent screen’ and follow the instructions. Once finished:
|
||||||
|
|
||||||
Select ‘Application type’ to be Web application.
|
Select ‘Application type’ to be Desktop application.
|
||||||
|
|
||||||
Enter an appropriate name.
|
Enter an appropriate name.
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ Input http://localhost/ for ‘Authorized redirect URIs’.
|
|||||||
|
|
||||||
Select the correct oauth scope:
|
Select the correct oauth scope:
|
||||||
|
|
||||||
- drive.file
|
- drive
|
||||||
- drive.install
|
- drive.install
|
||||||
|
|
||||||
Click ‘Create’.
|
Click ‘Create’.
|
||||||
|
31
README.md
31
README.md
@ -1,30 +1,7 @@
|
|||||||
# Pwnagotchi-Torch
|
# Pwnagotchi
|
||||||
<a href="https://github.com/jayofelony/pwnagotchi-bookworm/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/jayofelony/pwnagotchi-bookworm.svg"></a><br/>
|
This is the main source for all forks:
|
||||||
**This fork of [Pwnagotchi](https://www.pwnagotchi.ai) is only for 64-bit Raspberry Pi's. Such as the 02W, 3(b+) and 4(b) and the new Raspberry Pi 5!!.**
|
- RPiZeroW (32bit)
|
||||||
|
- RPiZero2W, RPi3, RPi4, RPi5 (64bit)
|
||||||
The RPi5 can only be used headless currently. (without display.)
|
|
||||||
|
|
||||||
|
|
||||||
If you are using an older 32-bit version Raspberry Pi, ZeroWH, use this [fork](https://github.com/jayofelony/pwnagotchi-torch/releases/tag/v2.6.4) and make sure you download the `armhf` version.
|
|
||||||
|
|
||||||
---
|
|
||||||
Download the latest image file [here](https://github.com/jayofelony/pwnagotchi-bookworm/releases/tag/v2.8.2), and let it auto-update from here on out.
|
|
||||||
|
|
||||||
**Use RPi imager to flash, please don't flash a new user as this will mess with logs created.**
|
|
||||||
- Select `Use Custom Image`
|
|
||||||
- Browse for the downloaded image file
|
|
||||||
- Select No under `Use OS Customization`
|
|
||||||
|
|
||||||
SSH credentials are `pi/raspberry`.
|
|
||||||
|
|
||||||
# Donations:
|
|
||||||
I would like to thank
|
|
||||||
- [findingmoist](https://github.com/findingmoist)
|
|
||||||
- [kr4k0n](https://github.com/kr4k0n)
|
|
||||||
|
|
||||||
for donating!
|
|
||||||
|
|
||||||
[Pwnagotchi-Torch](https://www.patreon.com/pwnagotchi_torch)
|
|
||||||
|
|
||||||
[GH Sponsor](https://github.com/sponsors/jayofelony)
|
[GH Sponsor](https://github.com/sponsors/jayofelony)
|
||||||
|
|
||||||
|
136
bin/pwnagotchi
136
bin/pwnagotchi
@ -7,6 +7,7 @@ import sys
|
|||||||
import toml
|
import toml
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
import pwnagotchi
|
import pwnagotchi
|
||||||
from pwnagotchi import utils
|
from pwnagotchi import utils
|
||||||
@ -50,7 +51,6 @@ def pwnagotchi_cli():
|
|||||||
|
|
||||||
agent.mode = 'auto'
|
agent.mode = 'auto'
|
||||||
agent.start()
|
agent.start()
|
||||||
config = agent.config()
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -60,7 +60,7 @@ def pwnagotchi_cli():
|
|||||||
channels = agent.get_access_points_by_channel()
|
channels = agent.get_access_points_by_channel()
|
||||||
# for each channel
|
# for each channel
|
||||||
for ch, aps in channels:
|
for ch, aps in channels:
|
||||||
time.sleep(0.2) # This is to make sure it doesn't error (https://github.com/seemoo-lab/nexmon/issues/596)
|
time.sleep(0.2)
|
||||||
agent.set_channel(ch)
|
agent.set_channel(ch)
|
||||||
|
|
||||||
if not agent.is_stale() and agent.any_activity():
|
if not agent.is_stale() and agent.any_activity():
|
||||||
@ -68,9 +68,6 @@ def pwnagotchi_cli():
|
|||||||
|
|
||||||
# for each ap on this channel
|
# for each ap on this channel
|
||||||
for ap in aps:
|
for ap in aps:
|
||||||
if ap['mac'][:13].lower in config['main']['whitelist'] or ap['hostname'] in config['main']['whitelist']:
|
|
||||||
logging.info(f"Found your MAC address {ap['mac']} - {config['main']['whitelist']}")
|
|
||||||
continue
|
|
||||||
# send an association frame in order to get for a PMKID
|
# send an association frame in order to get for a PMKID
|
||||||
agent.associate(ap)
|
agent.associate(ap)
|
||||||
# deauth all client stations in order to get a full handshake
|
# deauth all client stations in order to get a full handshake
|
||||||
@ -92,10 +89,8 @@ def pwnagotchi_cli():
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if str(e).find("wifi.interface not set") > 0:
|
if str(e).find("wifi.interface not set") > 0:
|
||||||
logging.exception(
|
logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e)
|
||||||
"main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e)
|
logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger")
|
||||||
logging.info(
|
|
||||||
"sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger")
|
|
||||||
time.sleep(60)
|
time.sleep(60)
|
||||||
agent.next_epoch()
|
agent.next_epoch()
|
||||||
else:
|
else:
|
||||||
@ -137,6 +132,8 @@ def pwnagotchi_cli():
|
|||||||
help="Print the configuration.")
|
help="Print the configuration.")
|
||||||
|
|
||||||
# Jayofelony added these
|
# Jayofelony added these
|
||||||
|
parser.add_argument('--wizard', dest="wizard", action="store_true", default=False,
|
||||||
|
help="Interactive installation of your personal configuration.")
|
||||||
parser.add_argument('--check-update', dest="check_update", action="store_true", default=False,
|
parser.add_argument('--check-update', dest="check_update", action="store_true", default=False,
|
||||||
help="Check for updates on Pwnagotchi. And tells current version.")
|
help="Check for updates on Pwnagotchi. And tells current version.")
|
||||||
parser.add_argument('--donate', dest="donate", action="store_true", default=False,
|
parser.add_argument('--donate', dest="donate", action="store_true", default=False,
|
||||||
@ -161,30 +158,135 @@ def pwnagotchi_cli():
|
|||||||
print(pwnagotchi.__version__)
|
print(pwnagotchi.__version__)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
if args.wizard:
|
||||||
|
def is_valid_hostname(hostname):
|
||||||
|
if len(hostname) > 255:
|
||||||
|
return False
|
||||||
|
if hostname[-1] == ".":
|
||||||
|
hostname = hostname[:-1] # strip exactly one dot from the right, if present
|
||||||
|
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
|
||||||
|
return all(allowed.match(x) for x in hostname.split("."))
|
||||||
|
|
||||||
|
pwn_restore = input("Do you want to restore the previous configuration?\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_restore in ('y', 'yes'):
|
||||||
|
os.system("cp -f /etc/pwnagotchi/config.toml.bak /etc/pwnagotchi/config.toml")
|
||||||
|
print("Your previous configuration is restored, and I will restart in 5 seconds.")
|
||||||
|
time.sleep(5)
|
||||||
|
os.system("service pwnagotchi restart")
|
||||||
|
else:
|
||||||
|
pwn_check = input("This will create a new configuration file and overwrite your current backup, are you sure?\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_check.lower() in ('y', 'yes'):
|
||||||
|
os.system("mv -f /etc/pwnagotchi/config.toml /etc/pwnagotchi/config.toml.bak")
|
||||||
|
with open("/etc/pwnagotchi/config.toml", "a+") as f:
|
||||||
|
f.write("# Do not edit this file if you do not know what you are doing!!!\n\n")
|
||||||
|
# Set pwnagotchi name
|
||||||
|
print("Welcome to the interactive installation of your personal Pwnagotchi configuration!\n"
|
||||||
|
"My name is Jayofelony, how may I call you?\n\n")
|
||||||
|
pwn_name = input("Pwnagotchi name (no spaces): ")
|
||||||
|
if pwn_name == "":
|
||||||
|
pwn_name = "Pwnagotchi"
|
||||||
|
print("I shall go by Pwnagotchi from now on!")
|
||||||
|
pwn_name = f"main.name = \"{pwn_name}\"\n"
|
||||||
|
f.write(pwn_name)
|
||||||
|
else:
|
||||||
|
if is_valid_hostname(pwn_name):
|
||||||
|
print(f"I shall go by {pwn_name} from now on!")
|
||||||
|
pwn_name = f"main.name = \"{pwn_name}\"\n"
|
||||||
|
f.write(pwn_name)
|
||||||
|
else:
|
||||||
|
print("You have chosen an invalid name. Please start over.")
|
||||||
|
exit()
|
||||||
|
pwn_whitelist = input("How many networks do you want to whitelist? "
|
||||||
|
"We will also ask a MAC for each network?\n"
|
||||||
|
"Each SSID and BSSID count as 1 network. \n\n"
|
||||||
|
"Be sure to use digits as your answer.\n\n"
|
||||||
|
"Amount of networks: ")
|
||||||
|
if int(pwn_whitelist) > 0:
|
||||||
|
f.write("main.whitelist = [\n")
|
||||||
|
for x in range(int(pwn_whitelist)):
|
||||||
|
ssid = input("SSID (Name): ")
|
||||||
|
bssid = input("BSSID (MAC): ")
|
||||||
|
f.write(f"\t\"{ssid}\",\n")
|
||||||
|
f.write(f"\t\"{bssid}\",\n")
|
||||||
|
f.write("]\n")
|
||||||
|
# set bluetooth tether
|
||||||
|
pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n"
|
||||||
|
"[Y/N] ")
|
||||||
|
if pwn_bluetooth.lower() in ('y', 'yes'):
|
||||||
|
f.write("main.plugins.bt-tether.enabled = true\n\n")
|
||||||
|
pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n"
|
||||||
|
"Device: ")
|
||||||
|
if pwn_bluetooth_device.lower() == "android":
|
||||||
|
f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n")
|
||||||
|
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
|
||||||
|
"MAC: ")
|
||||||
|
if pwn_bluetooth_mac != "":
|
||||||
|
f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||||
|
elif pwn_bluetooth_device.lower() == "ios":
|
||||||
|
f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n")
|
||||||
|
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
|
||||||
|
"MAC: ")
|
||||||
|
if pwn_bluetooth_mac != "":
|
||||||
|
f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||||
|
# set up display settings
|
||||||
|
pwn_display_enabled = input("Do you want to enable a display?\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_display_enabled.lower() in ('y', 'yes'):
|
||||||
|
f.write("ui.display.enabled = true\n")
|
||||||
|
pwn_display_type = input("What display do you use?\n\n"
|
||||||
|
"Be sure to check for the correct display type @ \n"
|
||||||
|
"https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L431\n\n"
|
||||||
|
"Display type: ")
|
||||||
|
if pwn_display_type != "":
|
||||||
|
f.write(f"ui.display.type = \"{pwn_display_type}\"\n")
|
||||||
|
pwn_display_invert = input("Do you want to invert the display colors?\n"
|
||||||
|
"N = Black background\n"
|
||||||
|
"Y = White background\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_display_invert.lower() in ('y', 'yes'):
|
||||||
|
f.write("ui.invert = true\n")
|
||||||
|
f.close()
|
||||||
|
if pwn_bluetooth.lower() in ('y', 'yes'):
|
||||||
|
if pwn_bluetooth_device.lower == "android":
|
||||||
|
print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n"
|
||||||
|
"Your configuration is done, and I will restart in 5 seconds.")
|
||||||
|
elif pwn_bluetooth_device.lower == "ios":
|
||||||
|
print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n"
|
||||||
|
"Your configuration is done, and I will restart in 5 seconds.")
|
||||||
|
else:
|
||||||
|
print("Your configuration is done, and I will restart in 5 seconds.")
|
||||||
|
time.sleep(5)
|
||||||
|
os.system("service pwnagotchi restart")
|
||||||
|
else:
|
||||||
|
print("Ok, doing nothing.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
if args.donate:
|
if args.donate:
|
||||||
print("Donations can made @ https://github.com/sponsors/jayofelony \n\nBut only if you really want to!")
|
print("Donations can be made @ \n "
|
||||||
|
"https://github.com/sponsors/jayofelony \n\n"
|
||||||
|
"But only if you really want to!")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if args.check_update:
|
if args.check_update:
|
||||||
resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi-bookworm/releases/latest")
|
resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest")
|
||||||
latest = resp.json()
|
latest = resp.json()
|
||||||
latest_ver = latest['tag_name'].replace('v', '')
|
latest_ver = latest['tag_name'].replace('v', '')
|
||||||
|
|
||||||
local = version_to_tuple(pwnagotchi.__version__)
|
local = version_to_tuple(pwnagotchi.__version__)
|
||||||
remote = version_to_tuple(latest_ver)
|
remote = version_to_tuple(latest_ver)
|
||||||
if remote > local:
|
if remote > local:
|
||||||
user_input = input("There is a new version available! Update from v%s to v%s?\n[y(es)/n(o)]"
|
user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (pwnagotchi.__version__, latest_ver))
|
||||||
% (pwnagotchi.__version__, latest_ver))
|
|
||||||
# input validation
|
# input validation
|
||||||
if user_input.lower() in ('y', 'yes'):
|
if user_input.lower() in ('y', 'yes'):
|
||||||
if os.path.exists('/root/.auto-update'):
|
if os.path.exists('/root/.auto-update'):
|
||||||
os.system("rm /root/.auto-update && systemctl restart pwnagotchi")
|
os.system("rm /root/.auto-update && systemctl restart pwnagotchi")
|
||||||
os.system("systemctl restart pwnagotchi")
|
else:
|
||||||
|
logging.error("You should make sure auto-update is enabled!")
|
||||||
print("Okay, give me a couple minutes. Just watch pwnlog while you wait.")
|
print("Okay, give me a couple minutes. Just watch pwnlog while you wait.")
|
||||||
elif user_input.lower() in ('n', 'no'):
|
elif user_input.lower() in ('n', 'no'): # using this elif for readability
|
||||||
print("Okay, guess not!")
|
print("Okay, guess not!")
|
||||||
else:
|
|
||||||
print("Invalid input.")
|
|
||||||
else:
|
else:
|
||||||
print("You are currently on the latest release, v%s." % pwnagotchi.__version__)
|
print("You are currently on the latest release, v%s." % pwnagotchi.__version__)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
173
builder/combined.json.pkr.hcl
Normal file
173
builder/combined.json.pkr.hcl
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
arm = {
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "github.com/cdecoux/builder-arm"
|
||||||
|
}
|
||||||
|
ansible = {
|
||||||
|
source = "github.com/hashicorp/ansible"
|
||||||
|
version = ">= 1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_hostname" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_version" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm" "rpi64-pwnagotchi" {
|
||||||
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz.sha256"
|
||||||
|
file_urls = ["https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz"]
|
||||||
|
file_checksum_type = "sha256"
|
||||||
|
file_target_extension = "xz"
|
||||||
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
image_path = "../pwnagotchi-64bit.img"
|
||||||
|
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||||
|
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||||
|
image_build_method = "resize"
|
||||||
|
image_size = "9G"
|
||||||
|
image_type = "dos"
|
||||||
|
image_partitions {
|
||||||
|
name = "boot"
|
||||||
|
type = "c"
|
||||||
|
start_sector = "8192"
|
||||||
|
filesystem = "fat"
|
||||||
|
size = "256M"
|
||||||
|
mountpoint = "/boot/firmware"
|
||||||
|
}
|
||||||
|
image_partitions {
|
||||||
|
name = "root"
|
||||||
|
type = "83"
|
||||||
|
start_sector = "532480"
|
||||||
|
filesystem = "ext4"
|
||||||
|
size = "0"
|
||||||
|
mountpoint = "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm" "rpi32-pwnagotchi" {
|
||||||
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz.sha256"
|
||||||
|
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"]
|
||||||
|
file_checksum_type = "sha256"
|
||||||
|
file_target_extension = "xz"
|
||||||
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
image_path = "../pwnagotchi-32bit.img"
|
||||||
|
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
|
||||||
|
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
|
||||||
|
image_build_method = "resize"
|
||||||
|
image_size = "9G"
|
||||||
|
image_type = "dos"
|
||||||
|
image_partitions {
|
||||||
|
name = "boot"
|
||||||
|
type = "c"
|
||||||
|
start_sector = "8192"
|
||||||
|
filesystem = "fat"
|
||||||
|
size = "256M"
|
||||||
|
mountpoint = "/boot"
|
||||||
|
}
|
||||||
|
image_partitions {
|
||||||
|
name = "root"
|
||||||
|
type = "83"
|
||||||
|
start_sector = "532480"
|
||||||
|
filesystem = "ext4"
|
||||||
|
size = "0"
|
||||||
|
mountpoint = "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# a build block invokes sources and runs provisioning steps on them. The
|
||||||
|
# documentation for build blocks can be found here:
|
||||||
|
# https://www.packer.io/docs/from-1.5/blocks/build
|
||||||
|
build {
|
||||||
|
name = "Raspberry Pi 64 Pwnagotchi"
|
||||||
|
sources = ["source.arm.rpi64-pwnagotchi"]
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/bin/"
|
||||||
|
sources = [
|
||||||
|
"data/64bit/usr/bin/bettercap-launcher",
|
||||||
|
"data/64bit/usr/bin/hdmioff",
|
||||||
|
"data/64bit/usr/bin/hdmion",
|
||||||
|
"data/64bit/usr/bin/monstart",
|
||||||
|
"data/64bit/usr/bin/monstop",
|
||||||
|
"data/64bit/usr/bin/pwnagotchi-launcher",
|
||||||
|
"data/64bit/usr/bin/pwnlib",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /usr/bin/*"]
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/systemd/system/"
|
||||||
|
sources = [
|
||||||
|
"data/64bit/etc/systemd/system/bettercap.service",
|
||||||
|
"data/64bit/etc/systemd/system/pwnagotchi.service",
|
||||||
|
"data/64bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
|
source = "data/64bit/etc/update-motd.d/01-motd"
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||||
|
}
|
||||||
|
provisioner "ansible-local" {
|
||||||
|
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||||
|
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||||
|
playbook_file = "raspberrypi64.yml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build {
|
||||||
|
name = "Raspberry Pi 32 Pwnagotchi"
|
||||||
|
sources = ["source.arm.rpi32-pwnagotchi"]
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/bin/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/usr/bin/bettercap-launcher",
|
||||||
|
"data/32bit/usr/bin/hdmioff",
|
||||||
|
"data/32bit/usr/bin/hdmion",
|
||||||
|
"data/32bit/usr/bin/monstart",
|
||||||
|
"data/32bit/usr/bin/monstop",
|
||||||
|
"data/32bit/usr/bin/pwnagotchi-launcher",
|
||||||
|
"data/32bit/usr/bin/pwnlib",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /usr/bin/*"]
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/systemd/system/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/etc/systemd/system/bettercap.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwnagotchi.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
|
source = "data/32bit/etc/update-motd.d/01-motd"
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||||
|
}
|
||||||
|
provisioner "ansible-local" {
|
||||||
|
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||||
|
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||||
|
playbook_dir = "extras/"
|
||||||
|
playbook_file = "raspberrypi32.yml"
|
||||||
|
}
|
||||||
|
}
|
67
builder/data/32bit/boot/config.txt
Normal file
67
builder/data/32bit/boot/config.txt
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# For more options and information see
|
||||||
|
# http://rptl.io/configtxt
|
||||||
|
# Some settings may impact device functionality. See link above for details
|
||||||
|
|
||||||
|
# Uncomment some or all of these to enable the optional hardware interfaces
|
||||||
|
#dtparam=i2c_arm=on
|
||||||
|
#dtparam=i2s=on
|
||||||
|
#dtparam=spi=on
|
||||||
|
|
||||||
|
# Enable audio (loads snd_bcm2835)
|
||||||
|
dtparam=audio=on
|
||||||
|
|
||||||
|
# Additional overlays and parameters are documented
|
||||||
|
# /boot/overlays/README
|
||||||
|
|
||||||
|
# Automatically load overlays for detected cameras
|
||||||
|
camera_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load overlays for detected DSI displays
|
||||||
|
display_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load initramfs files, if found
|
||||||
|
auto_initramfs=1
|
||||||
|
|
||||||
|
# Enable DRM VC4 V3D driver
|
||||||
|
dtoverlay=vc4-kms-v3d
|
||||||
|
max_framebuffers=2
|
||||||
|
|
||||||
|
# Don't have the firmware create an initial video= setting in cmdline.txt.
|
||||||
|
# Use the kernel's default instead.
|
||||||
|
disable_fw_kms_setup=1
|
||||||
|
|
||||||
|
# Run in 64-bit mode
|
||||||
|
arm_64bit=0
|
||||||
|
|
||||||
|
# Disable compensation for displays with overscan
|
||||||
|
disable_overscan=1
|
||||||
|
|
||||||
|
# Run as fast as firmware / board allows
|
||||||
|
arm_boost=1
|
||||||
|
|
||||||
|
[cm4]
|
||||||
|
# Enable host mode on the 2711 built-in XHCI USB controller.
|
||||||
|
# This line should be removed if the legacy DWC2 controller is required
|
||||||
|
# (e.g. for USB device mode) or if USB support is not required.
|
||||||
|
otg_mode=1
|
||||||
|
|
||||||
|
[all]
|
||||||
|
dtoverlay=dwc2
|
||||||
|
dtparam=i2c1=on
|
||||||
|
dtparam=i2c_arm=on
|
||||||
|
dtparam=spi=on
|
||||||
|
gpu_mem=1
|
||||||
|
dtoverlay=dwc2
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi0]
|
||||||
|
dtoverlay=spi1-3cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi3]
|
||||||
|
dtoverlay=spi1-3cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi4]
|
||||||
|
dtoverlay=spi1-3cs
|
||||||
|
#dtoverlay=disable-wifi
|
62
builder/data/32bit/etc/dhcpcd.conf
Normal file
62
builder/data/32bit/etc/dhcpcd.conf
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# A sample configuration for dhcpcd.
|
||||||
|
# See dhcpcd.conf(5) for details.
|
||||||
|
|
||||||
|
# Allow users of this group to interact with dhcpcd via the control socket.
|
||||||
|
#controlgroup wheel
|
||||||
|
|
||||||
|
# Inform the DHCP server of our hostname for DDNS.
|
||||||
|
hostname
|
||||||
|
|
||||||
|
# Use the hardware address of the interface for the Client ID.
|
||||||
|
clientid
|
||||||
|
# or
|
||||||
|
# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361.
|
||||||
|
# Some non-RFC compliant DHCP servers do not reply with this set.
|
||||||
|
# In this case, comment out duid and enable clientid above.
|
||||||
|
#duid
|
||||||
|
|
||||||
|
# Persist interface configuration when dhcpcd exits.
|
||||||
|
persistent
|
||||||
|
|
||||||
|
# Rapid commit support.
|
||||||
|
# Safe to enable by default because it requires the equivalent option set
|
||||||
|
# on the server to actually work.
|
||||||
|
option rapid_commit
|
||||||
|
|
||||||
|
# A list of options to request from the DHCP server.
|
||||||
|
option domain_name_servers, domain_name, domain_search, host_name
|
||||||
|
option classless_static_routes
|
||||||
|
# Respect the network MTU. This is applied to DHCP routes.
|
||||||
|
option interface_mtu
|
||||||
|
|
||||||
|
# Most distributions have NTP support.
|
||||||
|
#option ntp_servers
|
||||||
|
|
||||||
|
# A ServerID is required by RFC2131.
|
||||||
|
require dhcp_server_identifier
|
||||||
|
|
||||||
|
# Generate SLAAC address using the Hardware Address of the interface
|
||||||
|
#slaac hwaddr
|
||||||
|
# OR generate Stable Private IPv6 Addresses based from the DUID
|
||||||
|
slaac private
|
||||||
|
|
||||||
|
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
# !! DO NOT EDIT THESE LINES BELOW PLEASE !!
|
||||||
|
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
# static IP configuration:
|
||||||
|
denyinterfaces wlan0
|
||||||
|
|
||||||
|
interface eth0
|
||||||
|
static domain_name_servers=8.8.8.8 1.1.1.1
|
||||||
|
metric 201
|
||||||
|
|
||||||
|
interface usb0
|
||||||
|
static ip_address=10.0.0.2/24
|
||||||
|
static routers=10.0.0.1
|
||||||
|
static domain_name_servers=10.0.0.1 8.8.8.8 1.1.1.1
|
||||||
|
metric 202
|
||||||
|
|
||||||
|
interface bnep0
|
||||||
|
static domain_name_servers=8.8.8.8 1.1.1.1
|
||||||
|
metric 203
|
6
builder/data/32bit/etc/modules-load.d/modules.conf
Normal file
6
builder/data/32bit/etc/modules-load.d/modules.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# /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
|
40
builder/data/32bit/etc/profile
Normal file
40
builder/data/32bit/etc/profile
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
|
||||||
|
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
|
||||||
|
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
else
|
||||||
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games"
|
||||||
|
fi
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
if [ "${PS1-}" ]; then
|
||||||
|
if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
|
||||||
|
# The file bash.bashrc already sets the default PS1.
|
||||||
|
# PS1='\h:\w\$ '
|
||||||
|
if [ -f /etc/bash.bashrc ]; then
|
||||||
|
. /etc/bash.bashrc
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
PS1='# '
|
||||||
|
else
|
||||||
|
PS1='$ '
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d /etc/profile.d ]; then
|
||||||
|
for i in /etc/profile.d/*.sh; do
|
||||||
|
if [ -r $i ]; then
|
||||||
|
. $i
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
unset i
|
||||||
|
fi
|
||||||
|
alias custom='cd /usr/local/share/pwnagotchi/custom-plugins/'
|
||||||
|
alias config='sudo nano /etc/pwnagotchi/config.toml'
|
||||||
|
alias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered "s/,[[:digit:]]\\{3\\}\\]//g" | cut -d " " -f 2-'
|
||||||
|
alias pwnver='python3 -c "import pwnagotchi as p; print(p.__version__)"'
|
||||||
|
alias pwnkill='sudo killall -USR1 pwnagotchi'
|
||||||
|
|
16
builder/data/32bit/etc/rc.local
Normal file
16
builder/data/32bit/etc/rc.local
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
#
|
||||||
|
# rc.local
|
||||||
|
#
|
||||||
|
# This script is executed at the end of each multiuser runlevel.
|
||||||
|
# Make sure that the script will "exit 0" on success or any other
|
||||||
|
# value on error.
|
||||||
|
#
|
||||||
|
# In order to enable or disable this script just change the execution
|
||||||
|
# bits.
|
||||||
|
#
|
||||||
|
# By default this script does nothing.
|
||||||
|
|
||||||
|
timedatectl set-ntp true
|
||||||
|
|
||||||
|
exit 0
|
20
builder/data/32bit/etc/systemd/system/bluetooth.service
Normal file
20
builder/data/32bit/etc/systemd/system/bluetooth.service
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[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
|
@ -0,0 +1,6 @@
|
|||||||
|
[Unit]
|
||||||
|
After=hciuart.service bluetooth.service
|
||||||
|
Before=
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStartPre=/bin/sleep 5
|
19
builder/data/32bit/etc/systemd/system/pwnagotchi.service
Normal file
19
builder/data/32bit/etc/systemd/system/pwnagotchi.service
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
|
||||||
|
Documentation=https://pwnagotchi.ai
|
||||||
|
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
|
@ -8,7 +8,7 @@ After=bettercap.service
|
|||||||
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
|
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
|
||||||
Environment=LD_LIBRARY_PATH=/usr/local/lib
|
Environment=LD_LIBRARY_PATH=/usr/local/lib
|
||||||
Type=simple
|
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 /home/pi/logs/pwngrid-peer.log -iface wlan0mon
|
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
|
Restart=always
|
||||||
RestartSec=30
|
RestartSec=30
|
||||||
|
|
34
builder/data/32bit/etc/update-motd.d/01-motd
Executable file
34
builder/data/32bit/etc/update-motd.d/01-motd
Executable file
@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
_hostname=$(hostname)
|
||||||
|
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.9/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/"
|
28
builder/data/32bit/usr/bin/bettercap-launcher
Executable file
28
builder/data/32bit/usr/bin/bettercap-launcher
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
# check if wifi driver is bugged
|
||||||
|
if ! check_brcm; then
|
||||||
|
if ! reload_brcm; then
|
||||||
|
echo "Could not reload wifi driver. Reboot"
|
||||||
|
reboot
|
||||||
|
fi
|
||||||
|
sleep 10
|
||||||
|
fi
|
||||||
|
|
||||||
|
# start wlan0mon
|
||||||
|
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
|
149
builder/data/32bit/usr/bin/decryption-webserver
Executable file
149
builder/data/32bit/usr/bin/decryption-webserver
Executable file
@ -0,0 +1,149 @@
|
|||||||
|
#!/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()
|
@ -11,6 +11,7 @@ fi
|
|||||||
|
|
||||||
if is_auto_mode; then
|
if is_auto_mode; then
|
||||||
/usr/local/bin/pwnagotchi
|
/usr/local/bin/pwnagotchi
|
||||||
|
systemctl restart bettercap
|
||||||
else
|
else
|
||||||
/usr/local/bin/pwnagotchi --manual
|
/usr/local/bin/pwnagotchi --manual
|
||||||
fi
|
fi
|
184
builder/data/32bit/usr/bin/pwnlib
Executable file
184
builder/data/32bit/usr/bin/pwnlib
Executable file
@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# check if brcm is stuck
|
||||||
|
check_brcm() {
|
||||||
|
if [[ "$(journalctl -n10 -k --since -5m | grep -c 'brcmf_cfg80211_nexmon_set_channel.*Set Channel failed')" -ge 5 ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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() {
|
||||||
|
rfkill unblock all
|
||||||
|
ifconfig wlan0 up
|
||||||
|
iw dev wlan0 set power_save off
|
||||||
|
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor
|
||||||
|
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 specificed 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
|
||||||
|
}
|
70
builder/data/64bit/boot/firmware/config.txt
Normal file
70
builder/data/64bit/boot/firmware/config.txt
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# For more options and information see
|
||||||
|
# http://rptl.io/configtxt
|
||||||
|
# Some settings may impact device functionality. See link above for details
|
||||||
|
|
||||||
|
# Uncomment some or all of these to enable the optional hardware interfaces
|
||||||
|
#dtparam=i2c_arm=on
|
||||||
|
#dtparam=i2s=on
|
||||||
|
#dtparam=spi=on
|
||||||
|
|
||||||
|
# Enable audio (loads snd_bcm2835)
|
||||||
|
dtparam=audio=on
|
||||||
|
|
||||||
|
# Additional overlays and parameters are documented
|
||||||
|
# /boot/firmware/overlays/README
|
||||||
|
|
||||||
|
# Automatically load overlays for detected cameras
|
||||||
|
camera_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load overlays for detected DSI displays
|
||||||
|
display_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load initramfs files, if found
|
||||||
|
auto_initramfs=1
|
||||||
|
|
||||||
|
# Enable DRM VC4 V3D driver
|
||||||
|
dtoverlay=vc4-kms-v3d
|
||||||
|
max_framebuffers=2
|
||||||
|
|
||||||
|
# Don't have the firmware create an initial video= setting in cmdline.txt.
|
||||||
|
# Use the kernel's default instead.
|
||||||
|
disable_fw_kms_setup=1
|
||||||
|
|
||||||
|
# Run in 64-bit mode
|
||||||
|
arm_64bit=1
|
||||||
|
|
||||||
|
# Disable compensation for displays with overscan
|
||||||
|
disable_overscan=1
|
||||||
|
|
||||||
|
# Run as fast as firmware / board allows
|
||||||
|
arm_boost=1
|
||||||
|
|
||||||
|
[cm4]
|
||||||
|
# Enable host mode on the 2711 built-in XHCI USB controller.
|
||||||
|
# This line should be removed if the legacy DWC2 controller is required
|
||||||
|
# (e.g. for USB device mode) or if USB support is not required.
|
||||||
|
otg_mode=1
|
||||||
|
|
||||||
|
[all]
|
||||||
|
dtparam=i2c1=on
|
||||||
|
dtparam=i2c_arm=on
|
||||||
|
dtparam=spi=on
|
||||||
|
gpu_mem=1
|
||||||
|
dtoverlay=dwc2
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi0]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi3]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi4]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi5]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
#dtoverlay=disable-wifi
|
@ -0,0 +1,36 @@
|
|||||||
|
_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
|
26
builder/data/64bit/etc/dphys-swapfile
Normal file
26
builder/data/64bit/etc/dphys-swapfile
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# /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
|
6
builder/data/64bit/etc/modules-load.d/modules.conf
Normal file
6
builder/data/64bit/etc/modules-load.d/modules.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# /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
|
40
builder/data/64bit/etc/profile
Normal file
40
builder/data/64bit/etc/profile
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
|
||||||
|
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
|
||||||
|
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
else
|
||||||
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games"
|
||||||
|
fi
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
if [ "${PS1-}" ]; then
|
||||||
|
if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
|
||||||
|
# The file bash.bashrc already sets the default PS1.
|
||||||
|
# PS1='\h:\w\$ '
|
||||||
|
if [ -f /etc/bash.bashrc ]; then
|
||||||
|
. /etc/bash.bashrc
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
PS1='# '
|
||||||
|
else
|
||||||
|
PS1='$ '
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d /etc/profile.d ]; then
|
||||||
|
for i in /etc/profile.d/*.sh; do
|
||||||
|
if [ -r $i ]; then
|
||||||
|
. $i
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
unset i
|
||||||
|
fi
|
||||||
|
alias custom='cd /usr/local/share/pwnagotchi/custom-plugins/'
|
||||||
|
alias config='sudo nano /etc/pwnagotchi/config.toml'
|
||||||
|
alias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered "s/,[[:digit:]]\\{3\\}\\]//g" | cut -d " " -f 2-'
|
||||||
|
alias pwnver='python3 -c "import pwnagotchi as p; print(p.__version__)"'
|
||||||
|
alias pwnkill='sudo killall -USR1 pwnagotchi'
|
||||||
|
|
16
builder/data/64bit/etc/rc.local
Normal file
16
builder/data/64bit/etc/rc.local
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
#
|
||||||
|
# rc.local
|
||||||
|
#
|
||||||
|
# This script is executed at the end of each multiuser runlevel.
|
||||||
|
# Make sure that the script will "exit 0" on success or any other
|
||||||
|
# value on error.
|
||||||
|
#
|
||||||
|
# In order to enable or disable this script just change the execution
|
||||||
|
# bits.
|
||||||
|
#
|
||||||
|
# By default this script does nothing.
|
||||||
|
|
||||||
|
timedatectl set-ntp true
|
||||||
|
|
||||||
|
exit 0
|
13
builder/data/64bit/etc/systemd/system/bettercap.service
Normal file
13
builder/data/64bit/etc/systemd/system/bettercap.service
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[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
|
20
builder/data/64bit/etc/systemd/system/bluetooth.service
Normal file
20
builder/data/64bit/etc/systemd/system/bluetooth.service
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[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
|
16
builder/data/64bit/etc/systemd/system/pwngrid-peer.service
Normal file
16
builder/data/64bit/etc/systemd/system/pwngrid-peer.service
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=pwngrid peer service.
|
||||||
|
Documentation=https://pwnagotchi.ai
|
||||||
|
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
|
@ -2,7 +2,7 @@
|
|||||||
_hostname=$(hostname)
|
_hostname=$(hostname)
|
||||||
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.11/dist-packages/pwnagotchi/_version.py)
|
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.11/dist-packages/pwnagotchi/_version.py)
|
||||||
echo
|
echo
|
||||||
echo "(☉_☉ ) $_hostname"
|
echo "(◕‿‿◕) $_hostname"
|
||||||
echo
|
echo
|
||||||
echo " Hi! I'm a pwnagotchi $_version, please take good care of me!"
|
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 " Here are some basic things you need to know to raise me properly!"
|
||||||
@ -20,6 +20,7 @@ echo " I'm managed by systemd. Here are some basic commands."
|
|||||||
echo
|
echo
|
||||||
echo " If you want to know what I'm doing, you can check my logs with the command"
|
echo " If you want to know what I'm doing, you can check my logs with the command"
|
||||||
echo " - pwnlog"
|
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 --version, to check the current version"
|
||||||
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
|
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 " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
15
builder/data/64bit/root/settings.yaml
Normal file
15
builder/data/64bit/root/settings.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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
|
2
builder/data/64bit/usr/bin/hdmioff
Executable file
2
builder/data/64bit/usr/bin/hdmioff
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
sudo /usr/bin/tvservice -o
|
2
builder/data/64bit/usr/bin/hdmion
Executable file
2
builder/data/64bit/usr/bin/hdmion
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
sudo /usr/bin/tvservice -p
|
3
builder/data/64bit/usr/bin/monstart
Executable file
3
builder/data/64bit/usr/bin/monstart
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source /usr/bin/pwnlib
|
||||||
|
start_monitor_interface
|
3
builder/data/64bit/usr/bin/monstop
Executable file
3
builder/data/64bit/usr/bin/monstop
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source /usr/bin/pwnlib
|
||||||
|
stop_monitor_interface
|
17
builder/data/64bit/usr/bin/pwnagotchi-launcher
Executable file
17
builder/data/64bit/usr/bin/pwnagotchi-launcher
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/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
|
||||||
|
/usr/local/bin/pwnagotchi
|
||||||
|
systemctl restart bettercap
|
||||||
|
else
|
||||||
|
/usr/local/bin/pwnagotchi --manual
|
||||||
|
fi
|
@ -1,18 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# well ... it blinks the led
|
|
||||||
blink_led() {
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
for i in $(seq 1 "$1"); do
|
|
||||||
echo 0 >/sys/class/leds/led0/brightness
|
|
||||||
sleep 0.3
|
|
||||||
echo 1 >/sys/class/leds/led0/brightness
|
|
||||||
sleep 0.3
|
|
||||||
done
|
|
||||||
echo 0 >/sys/class/leds/led0/brightness
|
|
||||||
sleep 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
# reload mod
|
# reload mod
|
||||||
reload_brcm() {
|
reload_brcm() {
|
||||||
if ! modprobe -r brcmfmac; then
|
if ! modprobe -r brcmfmac; then
|
40
builder/extras/nexmon.yml
Normal file
40
builder/extras/nexmon.yml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
||||||
|
- name: clone nexmon repository
|
||||||
|
git:
|
||||||
|
repo: https://github.com/DrSchottky/nexmon.git
|
||||||
|
dest: /usr/local/src/nexmon
|
||||||
|
|
||||||
|
- name: make firmware
|
||||||
|
shell: "source ./setup_env.sh && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
|
||||||
|
- name: "make firmware patch ({{ item.name }})"
|
||||||
|
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/ && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ item.kernel }}"
|
||||||
|
ARCHFLAGS: "{{ item.arch_flags }}"
|
||||||
|
|
||||||
|
- name: "install new firmware ({{ item.name }})"
|
||||||
|
copy:
|
||||||
|
src: "/usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/{{ item.firmware }}"
|
||||||
|
dest: "/usr/lib/firmware/brcm/{{ item.firmware }}"
|
||||||
|
follow: true
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ item.kernel }}"
|
||||||
|
ARCHFLAGS: "{{ item.arch_flags }}"
|
||||||
|
|
||||||
|
- name: backup original driver
|
||||||
|
command: "mv /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||||
|
|
||||||
|
- name: copy modified driver
|
||||||
|
copy:
|
||||||
|
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_6.1.y-nexmon/brcmfmac.ko"
|
||||||
|
dest: "/usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||||
|
|
||||||
|
- name : load brcmfmac drivers
|
||||||
|
command: "/sbin/depmod -a {{ item.kernel }}"
|
94
builder/raspberrypi32.json.pkr.hcl
Normal file
94
builder/raspberrypi32.json.pkr.hcl
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
arm = {
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "github.com/cdecoux/builder-arm"
|
||||||
|
}
|
||||||
|
ansible = {
|
||||||
|
source = "github.com/hashicorp/ansible"
|
||||||
|
version = ">= 1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_hostname" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_version" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm" "rpi32-pwnagotchi" {
|
||||||
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz.sha256"
|
||||||
|
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"]
|
||||||
|
file_checksum_type = "sha256"
|
||||||
|
file_target_extension = "xz"
|
||||||
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
image_path = "../../pwnagotchi-32bit.img"
|
||||||
|
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
|
||||||
|
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
|
||||||
|
image_build_method = "resize"
|
||||||
|
image_size = "9G"
|
||||||
|
image_type = "dos"
|
||||||
|
image_partitions {
|
||||||
|
name = "boot"
|
||||||
|
type = "c"
|
||||||
|
start_sector = "8192"
|
||||||
|
filesystem = "fat"
|
||||||
|
size = "256M"
|
||||||
|
mountpoint = "/boot"
|
||||||
|
}
|
||||||
|
image_partitions {
|
||||||
|
name = "root"
|
||||||
|
type = "83"
|
||||||
|
start_sector = "532480"
|
||||||
|
filesystem = "ext4"
|
||||||
|
size = "0"
|
||||||
|
mountpoint = "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
build {
|
||||||
|
name = "Raspberry Pi 32 Pwnagotchi"
|
||||||
|
sources = ["source.arm.rpi32-pwnagotchi"]
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/bin/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/usr/bin/bettercap-launcher",
|
||||||
|
"data/32bit/usr/bin/hdmioff",
|
||||||
|
"data/32bit/usr/bin/hdmion",
|
||||||
|
"data/32bit/usr/bin/monstart",
|
||||||
|
"data/32bit/usr/bin/monstop",
|
||||||
|
"data/32bit/usr/bin/pwnagotchi-launcher",
|
||||||
|
"data/32bit/usr/bin/pwnlib",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /usr/bin/*"]
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/systemd/system/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/etc/systemd/system/bettercap.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwnagotchi.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
|
source = "data/32bit/etc/update-motd.d/01-motd"
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||||
|
}
|
||||||
|
provisioner "ansible-local" {
|
||||||
|
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||||
|
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||||
|
playbook_dir = "extras/"
|
||||||
|
playbook_file = "raspberrypi32.yml"
|
||||||
|
}
|
||||||
|
}
|
562
builder/raspberrypi32.yml
Normal file
562
builder/raspberrypi32.yml
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
---
|
||||||
|
- hosts:
|
||||||
|
- 127.0.0.1
|
||||||
|
gather_facts: true
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
boards:
|
||||||
|
- {
|
||||||
|
kernel: "6.1.21+",
|
||||||
|
name: "PiZeroW",
|
||||||
|
firmware: "brcmfmac43430-sdio.bin",
|
||||||
|
patch: "bcm43430a1/7_45_41_46",
|
||||||
|
cpu: arm1176,
|
||||||
|
arch_flags: "-arch armv6l"
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
kernel: "6.1.21-v7+",
|
||||||
|
name: "PiZero2W",
|
||||||
|
firmware: "brcmfmac43436-sdio.bin",
|
||||||
|
patch: "bcm43436b0/9_88_4_65",
|
||||||
|
cpu: any, #cortex-a53
|
||||||
|
arch_flags: "-arch armv7l"
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
kernel: "6.1.21-v7l+",
|
||||||
|
name: "Pi4b_32",
|
||||||
|
firmware: "brcmfmac43455-sdio.bin",
|
||||||
|
patch: "bcm43455c0/7_45_206",
|
||||||
|
cpu: any, #cortex-a72
|
||||||
|
arch_flags: "-arch armv7l"
|
||||||
|
}
|
||||||
|
kernel:
|
||||||
|
min: "6.1"
|
||||||
|
full: "6.1.21+"
|
||||||
|
full_2w: "6.1.21-v7+"
|
||||||
|
full_4b: "6.1.21-v7l+"
|
||||||
|
arch: "v6l"
|
||||||
|
pwnagotchi:
|
||||||
|
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||||
|
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
||||||
|
custom_plugin_dir: "/usr/local/share/pwnagotchi/custom-plugins"
|
||||||
|
services:
|
||||||
|
enable:
|
||||||
|
- bettercap.service
|
||||||
|
- bluetooth.service
|
||||||
|
- dphys-swapfile.service
|
||||||
|
- fstrim.timer
|
||||||
|
- pwnagotchi.service
|
||||||
|
- pwngrid-peer.service
|
||||||
|
disable:
|
||||||
|
- apt-daily-upgrade.service
|
||||||
|
- apt-daily-upgrade.timer
|
||||||
|
- apt-daily.service
|
||||||
|
- apt-daily.timer
|
||||||
|
- ifup@wlan0.service
|
||||||
|
- triggerhappy.service
|
||||||
|
- wpa_supplicant.service
|
||||||
|
packages:
|
||||||
|
caplets:
|
||||||
|
source: "https://github.com/jayofelony/caplets.git"
|
||||||
|
bettercap:
|
||||||
|
source: "https://github.com/jayofelony/bettercap.git"
|
||||||
|
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.2/bettercap-2.32.2-armhf.zip"
|
||||||
|
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
||||||
|
opwngrid:
|
||||||
|
source: "https://github.com/jayofelony/pwngrid.git"
|
||||||
|
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.7/pwngrid-1.10.7-armhf.zip"
|
||||||
|
torch:
|
||||||
|
wheel: "torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl"
|
||||||
|
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl"
|
||||||
|
torchvision:
|
||||||
|
wheel: "torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl"
|
||||||
|
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl"
|
||||||
|
apt:
|
||||||
|
downgrade:
|
||||||
|
- libpcap-dev_1.9.1-4_armhf.deb
|
||||||
|
- libpcap0.8-dbg_1.9.1-4_armhf.deb
|
||||||
|
- libpcap0.8-dev_1.9.1-4_armhf.deb
|
||||||
|
- libpcap0.8_1.9.1-4_armhf.deb
|
||||||
|
hold:
|
||||||
|
- firmware-atheros
|
||||||
|
- firmware-brcm80211
|
||||||
|
- firmware-libertas
|
||||||
|
- firmware-misc-nonfree
|
||||||
|
- firmware-realtek
|
||||||
|
- libpcap-dev
|
||||||
|
- libpcap0.8
|
||||||
|
- libpcap0.8-dev
|
||||||
|
- libpcap0.8-dbg
|
||||||
|
remove:
|
||||||
|
- avahi-daemon
|
||||||
|
- nfs-common
|
||||||
|
- triggerhappy
|
||||||
|
- wpasupplicant
|
||||||
|
install:
|
||||||
|
- autoconf
|
||||||
|
- bc
|
||||||
|
- bison
|
||||||
|
- bluez
|
||||||
|
- bluez-tools
|
||||||
|
- build-essential
|
||||||
|
- curl
|
||||||
|
- dkms
|
||||||
|
- dphys-swapfile
|
||||||
|
- espeak-ng
|
||||||
|
- evtest
|
||||||
|
- fbi
|
||||||
|
- flex
|
||||||
|
- fonts-dejavu
|
||||||
|
- fonts-dejavu-core
|
||||||
|
- fonts-dejavu-extra
|
||||||
|
- fonts-freefont-ttf
|
||||||
|
- g++
|
||||||
|
- gawk
|
||||||
|
- gcc-arm-none-eabi
|
||||||
|
- git
|
||||||
|
- libatlas-base-dev
|
||||||
|
- libavcodec58
|
||||||
|
- libavformat58
|
||||||
|
- libblas-dev
|
||||||
|
- libbluetooth-dev
|
||||||
|
- libbz2-dev
|
||||||
|
- libc-ares-dev
|
||||||
|
- libc6-dev
|
||||||
|
- libcpuinfo-dev
|
||||||
|
- libdbus-1-dev
|
||||||
|
- libdbus-glib-1-dev
|
||||||
|
- libeigen3-dev
|
||||||
|
- libelf-dev
|
||||||
|
- libffi-dev
|
||||||
|
- libfl-dev
|
||||||
|
- libfuse-dev
|
||||||
|
- libgdbm-dev
|
||||||
|
- libgl1-mesa-glx
|
||||||
|
- libgmp3-dev
|
||||||
|
- libgstreamer1.0-0
|
||||||
|
- libhdf5-dev
|
||||||
|
- liblapack-dev
|
||||||
|
- libncursesw5-dev
|
||||||
|
- libnetfilter-queue-dev
|
||||||
|
- libopenblas-dev
|
||||||
|
- libopenjp2-7
|
||||||
|
- libopenmpi-dev
|
||||||
|
- libopenmpi3
|
||||||
|
- libpcap-dev
|
||||||
|
- libprotobuf-dev
|
||||||
|
- libraspberrypi-bin
|
||||||
|
- libraspberrypi-dev
|
||||||
|
- libraspberrypi-doc
|
||||||
|
- libraspberrypi0
|
||||||
|
- libsleef-dev
|
||||||
|
- libsqlite3-dev
|
||||||
|
- libssl-dev
|
||||||
|
- libswscale5
|
||||||
|
- libtiff5
|
||||||
|
- libtool
|
||||||
|
- libts-bin
|
||||||
|
- libusb-1.0-0-dev
|
||||||
|
- lsof
|
||||||
|
- make
|
||||||
|
- ntp
|
||||||
|
- python3-dbus
|
||||||
|
- python3-flask
|
||||||
|
- python3-flask-cors
|
||||||
|
- python3-flaskext.wtf
|
||||||
|
- python3-pil
|
||||||
|
- python3-pip
|
||||||
|
- python3-protobuf
|
||||||
|
- python3-smbus
|
||||||
|
- qpdf
|
||||||
|
- raspberrypi-kernel-headers
|
||||||
|
- rsync
|
||||||
|
- screen
|
||||||
|
- tcpdump
|
||||||
|
- texinfo
|
||||||
|
- time
|
||||||
|
- tk-dev
|
||||||
|
- unzip
|
||||||
|
- vim
|
||||||
|
- wget
|
||||||
|
- wl
|
||||||
|
- xxd
|
||||||
|
- zlib1g-dev
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Create pi user
|
||||||
|
copy:
|
||||||
|
dest: /boot/userconf
|
||||||
|
content: |
|
||||||
|
pi:$6$3jNr0GA9KIyt4hmM$efeVIopdMQ8DGgEPCWWlbx3mJJNAYci1lEXGdlky0xPyjqwKNbwTL5SrCcpb4144C4IvzWjn7Iv.QjqmU7iyT/
|
||||||
|
|
||||||
|
- name: change hostname
|
||||||
|
lineinfile:
|
||||||
|
dest: /etc/hostname
|
||||||
|
regexp: '^raspberrypi'
|
||||||
|
line: "{{pwnagotchi.hostname}}"
|
||||||
|
state: present
|
||||||
|
when: lookup('file', '/etc/hostname') == "raspberrypi"
|
||||||
|
register: hostname
|
||||||
|
|
||||||
|
- name: add hostname to /etc/hosts
|
||||||
|
lineinfile:
|
||||||
|
dest: /etc/hosts
|
||||||
|
regexp: '^127\.0\.1\.1[ \t]+raspberrypi'
|
||||||
|
line: "127.0.1.1\t{{pwnagotchi.hostname}}"
|
||||||
|
state: present
|
||||||
|
when: hostname.changed
|
||||||
|
|
||||||
|
- name: Create custom plugin directory
|
||||||
|
file:
|
||||||
|
path: '{{ pwnagotchi.custom_plugin_dir }}'
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: remove current rc.local
|
||||||
|
file:
|
||||||
|
path: /etc/rc.local
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: update apt package cache
|
||||||
|
apt:
|
||||||
|
update_cache: yes
|
||||||
|
|
||||||
|
- name: install packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages.apt.install }}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: update pip3, setuptools, wheel
|
||||||
|
shell: "python3 -m pip install --upgrade pip setuptools wheel"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
#
|
||||||
|
# libpcap v1.9 - build from source
|
||||||
|
#
|
||||||
|
###########################################
|
||||||
|
|
||||||
|
# check for presence, then it can re-run in later parts if needed
|
||||||
|
# use the "make" built in
|
||||||
|
|
||||||
|
# install libpcap before bettercap and pwngrid, so they use it
|
||||||
|
- name: clone libpcap v1.9 from github
|
||||||
|
git:
|
||||||
|
repo: 'https://github.com/the-tcpdump-group/libpcap.git'
|
||||||
|
dest: /usr/local/src/libpcap
|
||||||
|
version: libpcap-1.9
|
||||||
|
|
||||||
|
- name: build and install libpcap into /usr/local/lib
|
||||||
|
shell: "./configure && make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/libpcap
|
||||||
|
|
||||||
|
- name: remove libpcap build folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/libpcap
|
||||||
|
|
||||||
|
- name: create symlink /usr/local/lib/libpcap.so.1.9.1
|
||||||
|
file:
|
||||||
|
src: /usr/local/lib/libpcap.so.1.9.1
|
||||||
|
dest: /usr/local/lib/libpcap.so.0.8
|
||||||
|
state: link
|
||||||
|
|
||||||
|
###############################################################
|
||||||
|
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
# Install nexmon for all boards
|
||||||
|
- name: build and install nexmon as needed
|
||||||
|
include_tasks: nexmon.yml
|
||||||
|
loop: "{{ boards }}"
|
||||||
|
|
||||||
|
# some pizero2w have the pizeroW wifi chip
|
||||||
|
# could this be a link instead of a copy? and force, only if not a link?
|
||||||
|
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
|
||||||
|
copy:
|
||||||
|
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||||
|
dest: /usr/lib/firmware/brcm/brcmfmac43436s-sdio.bin
|
||||||
|
follow: true
|
||||||
|
|
||||||
|
# delete blob files that make nexmon sad
|
||||||
|
- name: Delete the firmware blob files to avoid some nexmon crashing
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: '{{ item }}'
|
||||||
|
loop:
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430b0-sdio.raspberrypi,model-zero-2-w.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.raspberrypi,model-zero-2-w.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob
|
||||||
|
|
||||||
|
# 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: clone pwnagotchi repository
|
||||||
|
git:
|
||||||
|
repo: https://github.com/jayofelony/pwnagotchi.git
|
||||||
|
dest: /usr/local/src/pwnagotchi
|
||||||
|
register: pwnagotchigit
|
||||||
|
|
||||||
|
# is this even necessary? Can't we just link from /home/pi/pwnagotchi to /usr/local/{bin,lib,etc}
|
||||||
|
# then just git update in the home dir and encourage hacking?
|
||||||
|
# make owned by pi.pi, and custom plugins.
|
||||||
|
- name: build pwnagotchi wheel
|
||||||
|
command: "python3 setup.py sdist bdist_wheel"
|
||||||
|
args:
|
||||||
|
chdir: /usr/local/src/pwnagotchi
|
||||||
|
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
|
||||||
|
|
||||||
|
- name: download torch whl
|
||||||
|
get_url:
|
||||||
|
url: "{{ packages.torch.url }}"
|
||||||
|
dest: /usr/local/src/
|
||||||
|
|
||||||
|
- name: download torchvision whl
|
||||||
|
get_url:
|
||||||
|
url: "{{ packages.torchvision.url }}"
|
||||||
|
dest: /usr/local/src/
|
||||||
|
|
||||||
|
- name: install 32-bit pwnagotchi wheel and dependencies with 32-bit torch wheels
|
||||||
|
pip:
|
||||||
|
name:
|
||||||
|
- "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
|
||||||
|
- "{{ packages.torch.url }}"
|
||||||
|
- "{{ packages.torchvision.url }}"
|
||||||
|
extra_args: "--no-cache-dir"
|
||||||
|
environment:
|
||||||
|
QEMU_CPU: arm1176
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
|
||||||
|
|
||||||
|
- name: create /usr/local/share/pwnagotchi/ folder
|
||||||
|
file:
|
||||||
|
path: /usr/local/share/pwnagotchi/
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: remove pwnagotchi folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
|
- name: remove torch whl
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: "{{ lookup('fileglob', '/usr/local/src/torch*.whl') }}"
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
#
|
||||||
|
# pwngrid, bettercap
|
||||||
|
#
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
- name: Install go-1.21
|
||||||
|
unarchive:
|
||||||
|
src: https://go.dev/dl/go1.21.6.linux-armv6l.tar.gz
|
||||||
|
dest: /usr/local
|
||||||
|
remote_src: yes
|
||||||
|
register: golang
|
||||||
|
|
||||||
|
- name: Update .bashrc for go-1.21
|
||||||
|
blockinfile:
|
||||||
|
dest: /home/pi/.bashrc
|
||||||
|
state: present
|
||||||
|
block: |
|
||||||
|
export GOPATH=$HOME/go
|
||||||
|
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
||||||
|
when: golang.changed
|
||||||
|
|
||||||
|
- name: download pwngrid
|
||||||
|
unarchive:
|
||||||
|
remote_src: yes
|
||||||
|
src: "{{ packages.opwngrid.url }}"
|
||||||
|
dest: /usr/local/bin/
|
||||||
|
mode: 0755
|
||||||
|
|
||||||
|
- name: download and install bettercap
|
||||||
|
unarchive:
|
||||||
|
src: "{{ packages.bettercap.url }}"
|
||||||
|
dest: /usr/local/bin
|
||||||
|
remote_src: yes
|
||||||
|
exclude:
|
||||||
|
- README.md
|
||||||
|
- LICENSE.md
|
||||||
|
mode: 0755
|
||||||
|
|
||||||
|
- name: clone bettercap caplets
|
||||||
|
git:
|
||||||
|
repo: "{{ packages.caplets.source }}"
|
||||||
|
dest: /tmp/caplets
|
||||||
|
register: capletsgit
|
||||||
|
|
||||||
|
- name: install bettercap caplets
|
||||||
|
make:
|
||||||
|
chdir: /tmp/caplets
|
||||||
|
target: install
|
||||||
|
when: capletsgit.changed
|
||||||
|
|
||||||
|
- name: download and install bettercap ui
|
||||||
|
unarchive:
|
||||||
|
src: "{{ packages.bettercap.ui }}"
|
||||||
|
dest: /usr/local/share/bettercap/
|
||||||
|
remote_src: yes
|
||||||
|
mode: 0755
|
||||||
|
|
||||||
|
# to always have the bettercap webui available (because why not?)
|
||||||
|
- name: copy pwnagotchi-manual over pwnagotchi-auto caplet
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: /usr/local/share/bettercap/caplets/pwnagotchi-manual.cap
|
||||||
|
dest: /usr/local/share/bettercap/caplets/pwnagotchi-auto.cap
|
||||||
|
force: true
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: create /etc/pwnagotchi folder
|
||||||
|
file:
|
||||||
|
path: /etc/pwnagotchi
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: create log folder
|
||||||
|
file:
|
||||||
|
path: /home/pi/logs
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: check if user configuration exists
|
||||||
|
stat:
|
||||||
|
path: /etc/pwnagotchi/config.toml
|
||||||
|
register: user_config
|
||||||
|
|
||||||
|
- name: create /etc/pwnagotchi/config.toml
|
||||||
|
copy:
|
||||||
|
dest: /etc/pwnagotchi/config.toml
|
||||||
|
content: |
|
||||||
|
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||||
|
# Example:
|
||||||
|
# ui.display.enabled = true
|
||||||
|
# ui.display.type = "waveshare_4"
|
||||||
|
when: not user_config.stat.exists
|
||||||
|
|
||||||
|
- name: Delete motd 10-uname
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /etc/update-motd.d/10-uname
|
||||||
|
|
||||||
|
- name: enable ssh on boot
|
||||||
|
file:
|
||||||
|
path: /boot/ssh
|
||||||
|
state: touch
|
||||||
|
|
||||||
|
- name: change root partition
|
||||||
|
replace:
|
||||||
|
dest: /boot/cmdline.txt
|
||||||
|
backup: no
|
||||||
|
regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+"
|
||||||
|
replace: "root=/dev/mmcblk0p2"
|
||||||
|
|
||||||
|
- name: configure /boot/cmdline.txt
|
||||||
|
lineinfile:
|
||||||
|
path: /boot/cmdline.txt
|
||||||
|
backrefs: True
|
||||||
|
state: present
|
||||||
|
backup: no
|
||||||
|
regexp: '(.*)$'
|
||||||
|
line: '\1 modules-load=dwc2,g_ether'
|
||||||
|
|
||||||
|
- name: add firmware packages to hold
|
||||||
|
dpkg_selections:
|
||||||
|
name: "{{ item }}"
|
||||||
|
selection: hold
|
||||||
|
with_items: "{{ packages.apt.hold }}"
|
||||||
|
|
||||||
|
- name: disable unnecessary services
|
||||||
|
systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: stopped
|
||||||
|
enabled: no
|
||||||
|
with_items: "{{ services.disable }}"
|
||||||
|
|
||||||
|
- name: enable services
|
||||||
|
systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
enabled: true
|
||||||
|
state: stopped
|
||||||
|
with_items: "{{ services.enable }}"
|
||||||
|
|
||||||
|
#- name: remove golang build libraries
|
||||||
|
# file:
|
||||||
|
# state: absent
|
||||||
|
# path: /root/go
|
||||||
|
|
||||||
|
#- name: remove golang
|
||||||
|
# file:
|
||||||
|
# state: absent
|
||||||
|
# path: /usr/local/go
|
||||||
|
|
||||||
|
- name: make /root readable, becauase that's where all the files are
|
||||||
|
file:
|
||||||
|
path: /root
|
||||||
|
mode: '755'
|
||||||
|
|
||||||
|
- name: fix permissions on /home/pi
|
||||||
|
file:
|
||||||
|
path: /home/pi
|
||||||
|
owner: pi
|
||||||
|
group: pi
|
||||||
|
recurse: true
|
||||||
|
|
||||||
|
- name: remove unnecessary apt packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages.apt.remove }}"
|
||||||
|
state: absent
|
||||||
|
purge: yes
|
||||||
|
|
||||||
|
- name: remove dependencies that are no longer required
|
||||||
|
apt:
|
||||||
|
autoremove: yes
|
||||||
|
|
||||||
|
- name: clean apt cache
|
||||||
|
apt:
|
||||||
|
autoclean: true
|
||||||
|
|
||||||
|
- name: remove golang build libraries
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /root/go
|
||||||
|
|
||||||
|
- name: remove pre-collected packages zip
|
||||||
|
file:
|
||||||
|
path: /root/go_pkgs.tgz
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: remove golang
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/go
|
||||||
|
|
||||||
|
- name: remove /root/.cache (pip cache)
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /root/.cache
|
||||||
|
|
||||||
|
- name: remove ssh keys
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: "{{ item }}"
|
||||||
|
with_fileglob:
|
||||||
|
- "/etc/ssh/ssh_host*_key*"
|
||||||
|
|
||||||
|
- name: regenerate ssh keys
|
||||||
|
shell: "dpkg-reconfigure openssh-server"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: reload systemd services
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
@ -6,7 +6,7 @@ packer {
|
|||||||
}
|
}
|
||||||
ansible = {
|
ansible = {
|
||||||
source = "github.com/hashicorp/ansible"
|
source = "github.com/hashicorp/ansible"
|
||||||
version = "~> 1"
|
version = ">= 1.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -20,12 +20,12 @@ variable "pwn_version" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source "arm" "rpi64-pwnagotchi" {
|
source "arm" "rpi64-pwnagotchi" {
|
||||||
file_checksum_url = "https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz.sha256"
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz.sha256"
|
||||||
file_urls = ["https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz"]
|
file_urls = ["https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz"]
|
||||||
file_checksum_type = "sha256"
|
file_checksum_type = "sha256"
|
||||||
file_target_extension = "xz"
|
file_target_extension = "xz"
|
||||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
image_path = "../../../pwnagotchi-rpi-bookworm-${var.pwn_version}-arm64.img"
|
image_path = "../../../pwnagotchi-64bit.img"
|
||||||
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||||
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||||
image_build_method = "resize"
|
image_build_method = "resize"
|
||||||
@ -61,13 +61,13 @@ build {
|
|||||||
provisioner "file" {
|
provisioner "file" {
|
||||||
destination = "/usr/bin/"
|
destination = "/usr/bin/"
|
||||||
sources = [
|
sources = [
|
||||||
"data/usr/bin/bettercap-launcher",
|
"data/64bit/usr/bin/bettercap-launcher",
|
||||||
"data/usr/bin/hdmioff",
|
"data/64bit/usr/bin/hdmioff",
|
||||||
"data/usr/bin/hdmion",
|
"data/64bit/usr/bin/hdmion",
|
||||||
"data/usr/bin/monstart",
|
"data/64bit/usr/bin/monstart",
|
||||||
"data/usr/bin/monstop",
|
"data/64bit/usr/bin/monstop",
|
||||||
"data/usr/bin/pwnagotchi-launcher",
|
"data/64bit/usr/bin/pwnagotchi-launcher",
|
||||||
"data/usr/bin/pwnlib",
|
"data/64bit/usr/bin/pwnlib",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
provisioner "shell" {
|
provisioner "shell" {
|
||||||
@ -77,14 +77,14 @@ build {
|
|||||||
provisioner "file" {
|
provisioner "file" {
|
||||||
destination = "/etc/systemd/system/"
|
destination = "/etc/systemd/system/"
|
||||||
sources = [
|
sources = [
|
||||||
"data/etc/systemd/system/bettercap.service",
|
"data/64bit/etc/systemd/system/bettercap.service",
|
||||||
"data/etc/systemd/system/pwnagotchi.service",
|
"data/64bit/etc/systemd/system/pwnagotchi.service",
|
||||||
"data/etc/systemd/system/pwngrid-peer.service",
|
"data/64bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
provisioner "file" {
|
provisioner "file" {
|
||||||
destination = "/etc/update-motd.d/01-motd"
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
source = "data/etc/update-motd.d/01-motd"
|
source = "data/64bit/etc/update-motd.d/01-motd"
|
||||||
}
|
}
|
||||||
provisioner "shell" {
|
provisioner "shell" {
|
||||||
inline = ["chmod +x /etc/update-motd.d/*"]
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
@ -5,22 +5,12 @@
|
|||||||
become: true
|
become: true
|
||||||
vars:
|
vars:
|
||||||
kernel:
|
kernel:
|
||||||
min: "6.1"
|
min: "6.6"
|
||||||
full: "6.1.0-rpi8-rpi-v8"
|
full: "6.6.20+rpt-rpi-v8"
|
||||||
full_pi5: "6.1.0-rpi8-rpi-2712"
|
full_pi5: "6.6.20+rpt-rpi-2712"
|
||||||
pwnagotchi:
|
pwnagotchi:
|
||||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
|
||||||
system:
|
|
||||||
boot_options:
|
|
||||||
- "dtoverlay=dwc2"
|
|
||||||
- "dtoverlay=spi1-3cs"
|
|
||||||
- "dtparam=i2c1=on"
|
|
||||||
- "dtparam=i2c_arm=on"
|
|
||||||
- "dtparam=spi=on"
|
|
||||||
- "gpu_mem=16"
|
|
||||||
modules:
|
|
||||||
- "i2c-dev"
|
|
||||||
services:
|
services:
|
||||||
enable:
|
enable:
|
||||||
- bettercap.service
|
- bettercap.service
|
||||||
@ -74,6 +64,7 @@
|
|||||||
- bc
|
- bc
|
||||||
- bison
|
- bison
|
||||||
- bluez
|
- bluez
|
||||||
|
- bluez-tools
|
||||||
- build-essential
|
- build-essential
|
||||||
- curl
|
- curl
|
||||||
- dkms
|
- dkms
|
||||||
@ -137,6 +128,7 @@
|
|||||||
- libusb-1.0-0-dev
|
- libusb-1.0-0-dev
|
||||||
- lsof
|
- lsof
|
||||||
- make
|
- make
|
||||||
|
- ntp
|
||||||
- python3-dbus
|
- python3-dbus
|
||||||
- python3-flask
|
- python3-flask
|
||||||
- python3-flask-cors
|
- python3-flask-cors
|
||||||
@ -180,6 +172,12 @@
|
|||||||
update_cache: yes
|
update_cache: yes
|
||||||
install_recommends: false
|
install_recommends: false
|
||||||
|
|
||||||
|
- name: update pip3, setuptools, wheel
|
||||||
|
shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src
|
||||||
|
|
||||||
# Now we set up /boot/firmware
|
# Now we set up /boot/firmware
|
||||||
- name: Create pi user
|
- name: Create pi user
|
||||||
copy:
|
copy:
|
||||||
@ -192,12 +190,10 @@
|
|||||||
path: /boot/firmware/ssh
|
path: /boot/firmware/ssh
|
||||||
state: touch
|
state: touch
|
||||||
|
|
||||||
- name: adjust /boot/firmware/config.txt
|
- name: remove current rc.local
|
||||||
lineinfile:
|
file:
|
||||||
dest: /boot/firmware/config.txt
|
path: /etc/rc.local
|
||||||
insertafter: EOF
|
state: absent
|
||||||
line: '{{ item }}'
|
|
||||||
with_items: "{{ system.boot_options }}"
|
|
||||||
|
|
||||||
- name: change root partition
|
- name: change root partition
|
||||||
replace:
|
replace:
|
||||||
@ -459,7 +455,7 @@
|
|||||||
|
|
||||||
- name: clone pwnagotchi repository
|
- name: clone pwnagotchi repository
|
||||||
git:
|
git:
|
||||||
repo: https://github.com/jayofelony/pwnagotchi-bookworm.git
|
repo: https://github.com/jayofelony/pwnagotchi.git
|
||||||
dest: /usr/local/src/pwnagotchi
|
dest: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
- name: build pwnagotchi wheel
|
- name: build pwnagotchi wheel
|
||||||
@ -486,7 +482,7 @@
|
|||||||
|
|
||||||
- name: Update .bashrc for go-1.21
|
- name: Update .bashrc for go-1.21
|
||||||
blockinfile:
|
blockinfile:
|
||||||
dest: /home/pi/.bashrc
|
dest: /etc/profile
|
||||||
state: present
|
state: present
|
||||||
block: |
|
block: |
|
||||||
export GOPATH=$HOME/go
|
export GOPATH=$HOME/go
|
||||||
@ -567,11 +563,6 @@
|
|||||||
path: /etc/pwnagotchi
|
path: /etc/pwnagotchi
|
||||||
state: directory
|
state: directory
|
||||||
|
|
||||||
- name: create log folder
|
|
||||||
file:
|
|
||||||
path: /home/pi/logs
|
|
||||||
state: directory
|
|
||||||
|
|
||||||
- name: check if user configuration exists
|
- name: check if user configuration exists
|
||||||
stat:
|
stat:
|
||||||
path: /etc/pwnagotchi/config.toml
|
path: /etc/pwnagotchi/config.toml
|
||||||
@ -584,7 +575,7 @@
|
|||||||
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||||
# Example:
|
# Example:
|
||||||
# ui.display.enabled = true
|
# ui.display.enabled = true
|
||||||
# ui.display.type = "waveshare_2"
|
# ui.display.type = "waveshare_4"
|
||||||
when: not user_config.stat.exists
|
when: not user_config.stat.exists
|
||||||
|
|
||||||
- name: Delete motd
|
- name: Delete motd
|
||||||
@ -597,24 +588,6 @@
|
|||||||
state: absent
|
state: absent
|
||||||
path: /etc/update-motd.d/10-uname
|
path: /etc/update-motd.d/10-uname
|
||||||
|
|
||||||
- name: Add pwnlog alias
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnlog='tail -f -n300 /home/pi/logs/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: Add pwnver alias
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnver='python3 -c \"import pwnagotchi as p; print(p.__version__)\"'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: Add pwnkill alias to restart pwnagotchi with a signal
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnkill='sudo killall -USR1 pwnagotchi'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: add firmware packages to hold
|
- name: add firmware packages to hold
|
||||||
dpkg_selections:
|
dpkg_selections:
|
||||||
name: "{{ item }}"
|
name: "{{ item }}"
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = '2.8.4'
|
__version__ = '2.8.9'
|
||||||
|
@ -254,7 +254,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
|||||||
txt = '%d (%d)' % (len(self._handshakes), tot)
|
txt = '%d (%d)' % (len(self._handshakes), tot)
|
||||||
|
|
||||||
if self._last_pwnd is not None:
|
if self._last_pwnd is not None:
|
||||||
txt += ' [%s]' % self._last_pwnd[:11] # So it doesn't overlap with fix_brcmfmac_plugin
|
txt += ' [%s]' % self._last_pwnd
|
||||||
|
|
||||||
self._view.set('shakes', txt)
|
self._view.set('shakes', txt)
|
||||||
|
|
||||||
|
@ -23,6 +23,12 @@ def load(config, agent, epoch, from_disk=True):
|
|||||||
from stable_baselines3 import A2C
|
from stable_baselines3 import A2C
|
||||||
logging.debug("[AI] A2C imported in %.2fs" % (time.time() - start))
|
logging.debug("[AI] A2C imported in %.2fs" % (time.time() - start))
|
||||||
|
|
||||||
|
# remove invalid ai.parameters leftover from tensor_flow, if present
|
||||||
|
for key in [ 'alpha', 'epsilon', 'lr_schedule' ]:
|
||||||
|
if key in config['params']:
|
||||||
|
logging.info("Removing legacy ai parameter %s" % key);
|
||||||
|
del config['params'][key]
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
from stable_baselines3.a2c import MlpPolicy
|
from stable_baselines3.a2c import MlpPolicy
|
||||||
logging.debug("[AI] MlpPolicy imported in %.2fs" % (time.time() - start))
|
logging.debug("[AI] MlpPolicy imported in %.2fs" % (time.time() - start))
|
||||||
|
@ -100,7 +100,6 @@ class Client(object):
|
|||||||
await asyncio.sleep(sleep_time)
|
await asyncio.sleep(sleep_time)
|
||||||
continue
|
continue
|
||||||
except OSError:
|
except OSError:
|
||||||
sleep_time = min_sleep + max_sleep * random.random()
|
|
||||||
logging.warning('connection to the bettercap endpoint failed...')
|
logging.warning('connection to the bettercap endpoint failed...')
|
||||||
pwnagotchi.restart("AUTO")
|
pwnagotchi.restart("AUTO")
|
||||||
|
|
||||||
|
@ -18,10 +18,6 @@ main.custom_plugin_repos = [
|
|||||||
|
|
||||||
main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"
|
main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"
|
||||||
|
|
||||||
main.plugins.aircrackonly.enabled = true
|
|
||||||
|
|
||||||
main.plugins.hashie.enabled = true
|
|
||||||
|
|
||||||
main.plugins.auto-update.enabled = true
|
main.plugins.auto-update.enabled = true
|
||||||
main.plugins.auto-update.install = true
|
main.plugins.auto-update.install = true
|
||||||
main.plugins.auto-update.interval = 1
|
main.plugins.auto-update.interval = 1
|
||||||
@ -36,7 +32,7 @@ main.plugins.bt-tether.devices.android-phone.netmask = 24
|
|||||||
main.plugins.bt-tether.devices.android-phone.interval = 1
|
main.plugins.bt-tether.devices.android-phone.interval = 1
|
||||||
main.plugins.bt-tether.devices.android-phone.scantime = 10
|
main.plugins.bt-tether.devices.android-phone.scantime = 10
|
||||||
main.plugins.bt-tether.devices.android-phone.max_tries = 10
|
main.plugins.bt-tether.devices.android-phone.max_tries = 10
|
||||||
main.plugins.bt-tether.devices.android-phone.share_internet = false
|
main.plugins.bt-tether.devices.android-phone.share_internet = true
|
||||||
main.plugins.bt-tether.devices.android-phone.priority = 1
|
main.plugins.bt-tether.devices.android-phone.priority = 1
|
||||||
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.enabled = false
|
main.plugins.bt-tether.devices.ios-phone.enabled = false
|
||||||
@ -47,7 +43,7 @@ main.plugins.bt-tether.devices.ios-phone.netmask = 24
|
|||||||
main.plugins.bt-tether.devices.ios-phone.interval = 5
|
main.plugins.bt-tether.devices.ios-phone.interval = 5
|
||||||
main.plugins.bt-tether.devices.ios-phone.scantime = 20
|
main.plugins.bt-tether.devices.ios-phone.scantime = 20
|
||||||
main.plugins.bt-tether.devices.ios-phone.max_tries = 0
|
main.plugins.bt-tether.devices.ios-phone.max_tries = 0
|
||||||
main.plugins.bt-tether.devices.ios-phone.share_internet = false
|
main.plugins.bt-tether.devices.ios-phone.share_internet = true
|
||||||
main.plugins.bt-tether.devices.ios-phone.priority = 999
|
main.plugins.bt-tether.devices.ios-phone.priority = 999
|
||||||
|
|
||||||
main.plugins.fix_services.enabled = true
|
main.plugins.fix_services.enabled = true
|
||||||
@ -116,9 +112,7 @@ main.mon_stop_cmd = "/usr/bin/monstop"
|
|||||||
main.mon_max_blind_epochs = 50
|
main.mon_max_blind_epochs = 50
|
||||||
main.no_restart = false
|
main.no_restart = false
|
||||||
|
|
||||||
main.filter = ""
|
main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log"
|
||||||
|
|
||||||
main.log.path = "/home/pi/logs/pwnagotchi.log"
|
|
||||||
main.log.rotation.enabled = true
|
main.log.rotation.enabled = true
|
||||||
main.log.rotation.size = "10M"
|
main.log.rotation.size = "10M"
|
||||||
|
|
||||||
@ -158,6 +152,8 @@ personality.throttle_d = 0.9
|
|||||||
|
|
||||||
personality.clear_on_exit = true # clear display when shutting down cleanly
|
personality.clear_on_exit = true # clear display when shutting down cleanly
|
||||||
|
|
||||||
|
ui.invert = false # false = black background, true = white background
|
||||||
|
|
||||||
ui.fps = 0.0
|
ui.fps = 0.0
|
||||||
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
|
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
|
||||||
ui.font.size_offset = 0 # will be added to the font size
|
ui.font.size_offset = 0 # will be added to the font size
|
||||||
@ -187,6 +183,9 @@ ui.faces.debug = "(#__#)"
|
|||||||
ui.faces.upload = "(1__0)"
|
ui.faces.upload = "(1__0)"
|
||||||
ui.faces.upload1 = "(1__1)"
|
ui.faces.upload1 = "(1__1)"
|
||||||
ui.faces.upload2 = "(0__1)"
|
ui.faces.upload2 = "(0__1)"
|
||||||
|
ui.faces.png = false
|
||||||
|
ui.faces.position_x = 0
|
||||||
|
ui.faces.position_y = 34
|
||||||
|
|
||||||
ui.web.enabled = true
|
ui.web.enabled = true
|
||||||
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
|
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
|
||||||
@ -218,7 +217,7 @@ bettercap.silence = [
|
|||||||
|
|
||||||
fs.memory.enabled = true
|
fs.memory.enabled = true
|
||||||
fs.memory.mounts.log.enabled = true
|
fs.memory.mounts.log.enabled = true
|
||||||
fs.memory.mounts.log.mount = "/home/pi/logs"
|
fs.memory.mounts.log.mount = "/etc/pwnagotchi/log/"
|
||||||
fs.memory.mounts.log.size = "50M"
|
fs.memory.mounts.log.size = "50M"
|
||||||
fs.memory.mounts.log.sync = 60
|
fs.memory.mounts.log.sync = 60
|
||||||
fs.memory.mounts.log.zram = true
|
fs.memory.mounts.log.zram = true
|
||||||
|
@ -89,7 +89,7 @@ def update_data(last_session):
|
|||||||
'uname': subprocess.getoutput("uname -a"),
|
'uname': subprocess.getoutput("uname -a"),
|
||||||
'brain': brain,
|
'brain': brain,
|
||||||
'version': pwnagotchi.__version__,
|
'version': pwnagotchi.__version__,
|
||||||
'build': "Pwnagotchi-Torch by Jayofelony",
|
'build': "Pwnagotchi by Jayofelony",
|
||||||
'plugins': enabled,
|
'plugins': enabled,
|
||||||
'language': language,
|
'language': language,
|
||||||
'bettercap': subprocess.getoutput("bettercap -version").split(".\n\n")[1],
|
'bettercap': subprocess.getoutput("bettercap -version").split(".\n\n")[1],
|
||||||
|
Binary file not shown.
@ -36,18 +36,18 @@ msgid "The neural network is ready."
|
|||||||
msgstr "La red neuronal está lista."
|
msgstr "La red neuronal está lista."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr ""
|
msgstr "Generando llaves, no me apagues ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||||
msgstr "¡Oye, el canal {channel} está libre! Tu AP lo agradecerá."
|
msgstr "¡Oye, el canal {channel} está libre! Tu AP lo agradecerá."
|
||||||
|
|
||||||
msgid "Reading last session logs ..."
|
msgid "Reading last session logs ..."
|
||||||
msgstr ""
|
msgstr "Leyendo los logs de la última sesión ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Read {lines_so_far} log lines so far ..."
|
msgid "Read {lines_so_far} log lines so far ..."
|
||||||
msgstr ""
|
msgstr "Leyendo {lines_so_far} líneas de log hasta ahora ..."
|
||||||
|
|
||||||
msgid "I'm bored ..."
|
msgid "I'm bored ..."
|
||||||
msgstr "Estoy aburrido ..."
|
msgstr "Estoy aburrido ..."
|
||||||
@ -74,7 +74,7 @@ msgid "Leave me alone ..."
|
|||||||
msgstr "Me siento tan solo ..."
|
msgstr "Me siento tan solo ..."
|
||||||
|
|
||||||
msgid "I'm mad at you!"
|
msgid "I'm mad at you!"
|
||||||
msgstr ""
|
msgstr "Toy re enojado con vos!"
|
||||||
|
|
||||||
msgid "I'm living the life!"
|
msgid "I'm living the life!"
|
||||||
msgstr "¡Estoy viviendo la vida!"
|
msgstr "¡Estoy viviendo la vida!"
|
||||||
@ -97,11 +97,11 @@ msgstr "¡Hola {name}! Encantado de conocerte."
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Yo {name}! Sup?"
|
msgid "Yo {name}! Sup?"
|
||||||
msgstr ""
|
msgstr "Que onda {name}!?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey {name} how are you doing?"
|
msgid "Hey {name} how are you doing?"
|
||||||
msgstr ""
|
msgstr "Eh!, ¿Que haces {name}?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Unit {name} is nearby!"
|
msgid "Unit {name} is nearby!"
|
||||||
@ -127,10 +127,10 @@ msgid "Missed!"
|
|||||||
msgstr "¡Perdido!"
|
msgstr "¡Perdido!"
|
||||||
|
|
||||||
msgid "Good friends are a blessing!"
|
msgid "Good friends are a blessing!"
|
||||||
msgstr ""
|
msgstr "Lxs buenxs amigxs son una masa!"
|
||||||
|
|
||||||
msgid "I love my friends!"
|
msgid "I love my friends!"
|
||||||
msgstr ""
|
msgstr "¡Amo a mis amigxs!"
|
||||||
|
|
||||||
msgid "Nobody wants to play with me ..."
|
msgid "Nobody wants to play with me ..."
|
||||||
msgstr "Nadie quiere jugar conmigo ..."
|
msgstr "Nadie quiere jugar conmigo ..."
|
||||||
@ -143,7 +143,7 @@ msgstr "¡¿Dónde está todo el mundo?!"
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Napping for {secs}s ..."
|
msgid "Napping for {secs}s ..."
|
||||||
msgstr "Descansando durante {secs}s ..."
|
msgstr "Descansando por {secs}s ..."
|
||||||
|
|
||||||
msgid "Zzzzz"
|
msgid "Zzzzz"
|
||||||
msgstr "Zzzzz"
|
msgstr "Zzzzz"
|
||||||
@ -203,7 +203,7 @@ msgstr "Oops, algo salió mal ... Reiniciando ..."
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uploading data to {to} ..."
|
msgid "Uploading data to {to} ..."
|
||||||
msgstr ""
|
msgstr "Subiendo data a {to} ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Downloading from {name} ..."
|
msgid "Downloading from {name} ..."
|
||||||
|
Binary file not shown.
@ -248,7 +248,7 @@ msgid "minutes"
|
|||||||
msgstr "minuten"
|
msgstr "minuten"
|
||||||
|
|
||||||
msgid "seconds"
|
msgid "seconds"
|
||||||
msgstr "seconds"
|
msgstr "sekondes"
|
||||||
|
|
||||||
msgid "hour"
|
msgid "hour"
|
||||||
msgstr "oere"
|
msgstr "oere"
|
||||||
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-01-25 23:40+0100\n"
|
"POT-Creation-Date: 2024-02-16 15:26-0300\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -199,6 +199,9 @@ def list_plugins(args, config, pattern='*'):
|
|||||||
available_not_installed = set(available.keys()) - set(installed.keys())
|
available_not_installed = set(available.keys()) - set(installed.keys())
|
||||||
|
|
||||||
max_len_list = available_and_installed if args.installed else available_not_installed
|
max_len_list = available_and_installed if args.installed else available_not_installed
|
||||||
|
if not max_len_list:
|
||||||
|
print('Maybe try: sudo pwnagotchi plugins update')
|
||||||
|
return 1
|
||||||
max_len = max(map(len, max_len_list))
|
max_len = max(map(len, max_len_list))
|
||||||
header = line.format(name='Plugin', width=max_len, version='Version', enabled='Active', status='Status')
|
header = line.format(name='Plugin', width=max_len, version='Version', enabled='Active', status='Status')
|
||||||
line_length = max(max_len, len('Plugin')) + len(header) - len('Plugin') - 12 # lol
|
line_length = max(max_len, len('Plugin')) + len(header) - len('Plugin') - 12 # lol
|
||||||
@ -239,7 +242,7 @@ def list_plugins(args, config, pattern='*'):
|
|||||||
print('-' * line_length)
|
print('-' * line_length)
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
logging.info('Maybe try: pwnagotchi plugins update')
|
print('Maybe try: sudo pwnagotchi plugins update')
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
import pwnagotchi.plugins as plugins
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import subprocess
|
|
||||||
import string
|
|
||||||
import os
|
|
||||||
|
|
||||||
'''
|
|
||||||
Aircrack-ng needed, to install:
|
|
||||||
> apt-get install aircrack-ng
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class AircrackOnly(plugins.Plugin):
|
|
||||||
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
|
|
||||||
__version__ = '1.0.1'
|
|
||||||
__license__ = 'GPL3'
|
|
||||||
__description__ = 'confirm pcap contains handshake/PMKID or delete it'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.text_to_set = ""
|
|
||||||
self.options = dict()
|
|
||||||
|
|
||||||
def on_ready(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
def on_loaded(self):
|
|
||||||
logging.info("aircrackonly plugin loaded")
|
|
||||||
|
|
||||||
if 'face' not in self.options:
|
|
||||||
self.options['face'] = '(>.<)'
|
|
||||||
|
|
||||||
check = subprocess.run(
|
|
||||||
'/usr/bin/dpkg -l aircrack-ng | grep aircrack-ng | awk \'{print $2, $3}\'', shell=True,
|
|
||||||
stdout=subprocess.PIPE)
|
|
||||||
check = check.stdout.decode('utf-8').strip()
|
|
||||||
if check != "aircrack-ng <none>":
|
|
||||||
logging.info("aircrackonly: Found " + check)
|
|
||||||
else:
|
|
||||||
logging.warning("aircrack-ng is not installed!")
|
|
||||||
|
|
||||||
def on_handshake(self, agent, filename, access_point, client_station):
|
|
||||||
display = agent.view()
|
|
||||||
to_delete = 0
|
|
||||||
handshake_found = 0
|
|
||||||
|
|
||||||
result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''),
|
|
||||||
shell=True, stdout=subprocess.PIPE)
|
|
||||||
result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
|
|
||||||
if result:
|
|
||||||
handshake_found = 1
|
|
||||||
logging.info("[AircrackOnly] contains handshake")
|
|
||||||
|
|
||||||
if handshake_found == 0:
|
|
||||||
result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "PMKID" | awk \'{print $2}\''),
|
|
||||||
shell=True, stdout=subprocess.PIPE)
|
|
||||||
result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
|
|
||||||
if result:
|
|
||||||
logging.info("[AircrackOnly] contains PMKID")
|
|
||||||
else:
|
|
||||||
to_delete = 1
|
|
||||||
|
|
||||||
if to_delete == 1:
|
|
||||||
os.remove(filename)
|
|
||||||
self.text_to_set = "Removed an uncrackable pcap"
|
|
||||||
logging.warning("Removed uncrackable pcap " + filename)
|
|
||||||
display.update(force=True)
|
|
||||||
|
|
||||||
def on_ui_update(self, ui):
|
|
||||||
if self.text_to_set:
|
|
||||||
ui.set('face', self.options['face'])
|
|
||||||
ui.set('status', self.text_to_set)
|
|
||||||
self.text_to_set = ""
|
|
@ -28,7 +28,8 @@ def check(version, repo, native=True):
|
|||||||
resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo)
|
resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo)
|
||||||
latest = resp.json()
|
latest = resp.json()
|
||||||
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
||||||
is_arm64 = info['arch'].startswith('aarch')
|
is_armhf = info['arch'].startswith('arm')
|
||||||
|
is_aarch = info['arch'].startswith('aarch')
|
||||||
|
|
||||||
local = version_to_tuple(info['current'])
|
local = version_to_tuple(info['current'])
|
||||||
remote = version_to_tuple(latest_ver)
|
remote = version_to_tuple(latest_ver)
|
||||||
@ -36,12 +37,20 @@ def check(version, repo, native=True):
|
|||||||
if not native:
|
if not native:
|
||||||
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
|
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
|
||||||
else:
|
else:
|
||||||
if is_arm64:
|
if is_armhf:
|
||||||
# check if this release is compatible with aarch64
|
# check if this release is compatible with armhf
|
||||||
for asset in latest['assets']:
|
for asset in latest['assets']:
|
||||||
download_url = asset['browser_download_url']
|
download_url = asset['browser_download_url']
|
||||||
if (download_url.endswith('.zip') and
|
if (download_url.endswith('.zip') and
|
||||||
(info['arch'] in download_url or (is_arm64 and 'aarch64' in download_url))):
|
(info['arch'] in download_url or (is_armhf and 'armhf' in download_url))):
|
||||||
|
info['url'] = download_url
|
||||||
|
break
|
||||||
|
elif is_aarch:
|
||||||
|
# check if this release is compatible with arm64/aarch64
|
||||||
|
for asset in latest['assets']:
|
||||||
|
download_url = asset['browser_download_url']
|
||||||
|
if (download_url.endswith('.zip') and
|
||||||
|
(info['arch'] in download_url or (is_aarch and 'aarch' in download_url))):
|
||||||
info['url'] = download_url
|
info['url'] = download_url
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -189,7 +198,7 @@ class AutoUpdate(plugins.Plugin):
|
|||||||
to_check = [
|
to_check = [
|
||||||
('jayofelony/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
|
('jayofelony/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
|
||||||
('jayofelony/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
|
('jayofelony/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
|
||||||
('jayofelony/pwnagotchi-bookworm', pwnagotchi.__version__, False, 'pwnagotchi')
|
('jayofelony/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
|
||||||
]
|
]
|
||||||
|
|
||||||
for repo, local_version, is_native, svc_name in to_check:
|
for repo, local_version, is_native, svc_name in to_check:
|
||||||
@ -221,7 +230,7 @@ class AutoUpdate(plugins.Plugin):
|
|||||||
if num_installed > 0:
|
if num_installed > 0:
|
||||||
display.update(force=True, new_data={'status': 'Rebooting ...'})
|
display.update(force=True, new_data={'status': 'Rebooting ...'})
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
os.system("service pwnagotchi restart")
|
pwnagotchi.reboot()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("[update] %s" % e)
|
logging.error("[update] %s" % e)
|
||||||
|
@ -4,6 +4,7 @@ import subprocess
|
|||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
from io import TextIOWrapper
|
from io import TextIOWrapper
|
||||||
|
import os
|
||||||
|
|
||||||
import pwnagotchi
|
import pwnagotchi
|
||||||
from pwnagotchi import plugins
|
from pwnagotchi import plugins
|
||||||
@ -11,6 +12,10 @@ from pwnagotchi import plugins
|
|||||||
import pwnagotchi.ui.faces as faces
|
import pwnagotchi.ui.faces as faces
|
||||||
from pwnagotchi.bettercap import Client
|
from pwnagotchi.bettercap import Client
|
||||||
|
|
||||||
|
from pwnagotchi.ui.components import Text
|
||||||
|
from pwnagotchi.ui.view import BLACK
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
|
||||||
|
|
||||||
class FixServices(plugins.Plugin):
|
class FixServices(plugins.Plugin):
|
||||||
__author__ = 'jayofelony'
|
__author__ = 'jayofelony'
|
||||||
@ -21,19 +26,18 @@ class FixServices(plugins.Plugin):
|
|||||||
__help__ = """
|
__help__ = """
|
||||||
Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG.
|
Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG.
|
||||||
"""
|
"""
|
||||||
__defaults__ = {
|
|
||||||
'enabled': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.options = dict()
|
self.options = dict()
|
||||||
self.pattern1 = re.compile(r'wifi error while hopping to channel')
|
self.pattern = re.compile(r'brcmf_cfg80211_nexmon_set_channel.*?Set Channel failed')
|
||||||
self.pattern2 = re.compile(r'Firmware has halted or crashed')
|
self.pattern2 = re.compile(r'wifi error while hopping to channel')
|
||||||
self.pattern3 = re.compile(r'error 400: could not find interface wlan0mon')
|
self.pattern3 = re.compile(r'Firmware has halted or crashed')
|
||||||
|
self.pattern4 = re.compile(r'error 400: could not find interface wlan0mon')
|
||||||
|
self.pattern5 = re.compile(r'fatal error: concurrent map iteration and map write')
|
||||||
|
self.pattern6 = re.compile(r'panic: runtime error')
|
||||||
self.isReloadingMon = False
|
self.isReloadingMon = False
|
||||||
self.connection = None
|
self.connection = None
|
||||||
self.LASTTRY = 0
|
self.LASTTRY = 0
|
||||||
self._count = 0
|
|
||||||
|
|
||||||
def on_loaded(self):
|
def on_loaded(self):
|
||||||
"""
|
"""
|
||||||
@ -42,14 +46,27 @@ class FixServices(plugins.Plugin):
|
|||||||
logging.info("[Fix_Services] plugin loaded.")
|
logging.info("[Fix_Services] plugin loaded.")
|
||||||
|
|
||||||
def on_ready(self, agent):
|
def on_ready(self, agent):
|
||||||
last_lines = self.get_last_lines('journalctl', ['-n10', '-k'], 10)
|
last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'],
|
||||||
|
stdout=subprocess.PIPE).stdout))[-10:])
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
||||||
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||||
if ",UP," in str(cmd_output):
|
if ",UP," in str(cmd_output):
|
||||||
logging.info("wlan0mon is up.")
|
logging.debug("wlan0mon is up.")
|
||||||
|
|
||||||
logging.info("[Fix_Services] Logs look good!")
|
if len(self.pattern.findall(last_lines)) >= 3:
|
||||||
|
if hasattr(agent, 'view'):
|
||||||
|
display = agent.view()
|
||||||
|
display.set('status', 'Blind-Bug detected. Restarting.')
|
||||||
|
display.update(force=True)
|
||||||
|
logging.debug('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||||
|
try:
|
||||||
|
self._tryTurningItOffAndOnAgain(agent)
|
||||||
|
except Exception as err:
|
||||||
|
logging.warning("[Fix_Services turnOffAndOn] %s" % repr(err))
|
||||||
|
|
||||||
|
else:
|
||||||
|
logging.debug("[Fix_Services] Logs look good!")
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err))
|
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err))
|
||||||
@ -63,12 +80,12 @@ class FixServices(plugins.Plugin):
|
|||||||
# apparently this only gets messages from bettercap going to syslog, not from syslog
|
# apparently this only gets messages from bettercap going to syslog, not from syslog
|
||||||
def on_bcap_sys_log(self, agent, event):
|
def on_bcap_sys_log(self, agent, event):
|
||||||
if re.search('wifi error while hopping to channel', event['data']['Message']):
|
if re.search('wifi error while hopping to channel', event['data']['Message']):
|
||||||
logging.info("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message'])
|
logging.debug("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message'])
|
||||||
logging.info("[Fix_Services]**** restarting wifi.recon")
|
logging.debug("[Fix_Services]**** restarting wifi.recon")
|
||||||
try:
|
try:
|
||||||
result = agent.run("wifi.recon off; wifi.recon on")
|
result = agent.run("wifi.recon off; wifi.recon on")
|
||||||
if result["success"]:
|
if result["success"]:
|
||||||
logging.info("[Fix_Services] wifi.recon flip: success!")
|
logging.debug("[Fix_Services] wifi.recon flip: success!")
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
if display:
|
if display:
|
||||||
@ -82,23 +99,14 @@ class FixServices(plugins.Plugin):
|
|||||||
logging.error("[Fix_Services]SYSLOG wifi.recon flip fail: %s" % err)
|
logging.error("[Fix_Services]SYSLOG wifi.recon flip fail: %s" % err)
|
||||||
self._tryTurningItOffAndOnAgain(agent)
|
self._tryTurningItOffAndOnAgain(agent)
|
||||||
|
|
||||||
def get_last_lines(self, command, args, n):
|
|
||||||
try:
|
|
||||||
process = subprocess.Popen([command] + args, stdout=subprocess.PIPE)
|
|
||||||
output = TextIOWrapper(process.stdout)
|
|
||||||
lines = output.readlines()
|
|
||||||
last_n_lines = ''.join(lines[-n:])
|
|
||||||
return last_n_lines
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error occurred: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def on_epoch(self, agent, epoch, epoch_data):
|
def on_epoch(self, agent, epoch, epoch_data):
|
||||||
|
last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'],
|
||||||
last_lines = self.get_last_lines('journalctl', ['-n10', '-k'], 10)
|
stdout=subprocess.PIPE).stdout))[-10:])
|
||||||
other_last_lines = self.get_last_lines('journalctl', ['-n10'], 10)
|
other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'],
|
||||||
other_other_last_lines = self.get_last_lines('tail', ['-n10', '/home/pi/logs/pwnagotchi.log'], 10)
|
stdout=subprocess.PIPE).stdout))[-10:])
|
||||||
|
other_other_last_lines = ''.join(
|
||||||
|
list(TextIOWrapper(subprocess.Popen(['tail', '-n10', '/var/log/pwnagotchi.log'],
|
||||||
|
stdout=subprocess.PIPE).stdout))[-10:])
|
||||||
# don't check if we ran a reset recently
|
# don't check if we ran a reset recently
|
||||||
logging.debug("[Fix_Services]**** epoch")
|
logging.debug("[Fix_Services]**** epoch")
|
||||||
if time.time() - self.LASTTRY > 180:
|
if time.time() - self.LASTTRY > 180:
|
||||||
@ -108,18 +116,31 @@ class FixServices(plugins.Plugin):
|
|||||||
logging.debug("[Fix_Services]**** checking")
|
logging.debug("[Fix_Services]**** checking")
|
||||||
|
|
||||||
# Look for pattern 1
|
# Look for pattern 1
|
||||||
if len(self.pattern1.findall(other_last_lines)) >= 5:
|
if len(self.pattern.findall(last_lines)) >= 3:
|
||||||
|
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||||
|
if hasattr(agent, 'view'):
|
||||||
|
display = agent.view()
|
||||||
|
display.set('status', 'Blind-Bug detected. Restarting.')
|
||||||
|
display.update(force=True)
|
||||||
|
logging.debug('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||||
|
try:
|
||||||
|
self._tryTurningItOffAndOnAgain(agent)
|
||||||
|
except Exception as err:
|
||||||
|
logging.warning("[Fix_Services] TTOAOA: %s" % repr(err))
|
||||||
|
|
||||||
|
# Look for pattern 2
|
||||||
|
elif len(self.pattern2.findall(other_last_lines)) >= 5:
|
||||||
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
display.set('status', 'Wifi channel stuck. Restarting recon.')
|
display.set('status', 'Wifi channel stuck. Restarting recon.')
|
||||||
display.update(force=True)
|
display.update(force=True)
|
||||||
logging.info('[Fix_Services] Wifi channel stuck. Restarting recon.')
|
logging.debug('[Fix_Services] Wifi channel stuck. Restarting recon.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = agent.run("wifi.recon off; wifi.recon on")
|
result = agent.run("wifi.recon off; wifi.recon on")
|
||||||
if result["success"]:
|
if result["success"]:
|
||||||
logging.info("[Fix_Services] wifi.recon flip: success!")
|
logging.debug("[Fix_Services] wifi.recon flip: success!")
|
||||||
if display:
|
if display:
|
||||||
display.update(force=True, new_data={"status": "Wifi recon flipped!",
|
display.update(force=True, new_data={"status": "Wifi recon flipped!",
|
||||||
"face": faces.COOL})
|
"face": faces.COOL})
|
||||||
@ -131,9 +152,9 @@ class FixServices(plugins.Plugin):
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services wifi.recon flip] %s" % repr(err))
|
logging.error("[Fix_Services wifi.recon flip] %s" % repr(err))
|
||||||
|
|
||||||
# Look for pattern 2
|
# Look for pattern 3
|
||||||
elif len(self.pattern2.findall(other_last_lines)) >= 1:
|
elif len(self.pattern3.findall(other_last_lines)) >= 1:
|
||||||
logging.info("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.")
|
logging.debug("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.")
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
|
display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
|
||||||
@ -145,9 +166,9 @@ class FixServices(plugins.Plugin):
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||||
|
|
||||||
# Look for pattern 3
|
# Look for pattern 4
|
||||||
elif len(self.pattern3.findall(other_other_last_lines)) >= 3:
|
elif len(self.pattern4.findall(other_other_last_lines)) >= 3:
|
||||||
logging.info("[Fix_Services] wlan0 is down!")
|
logging.debug("[Fix_Services] wlan0 is down!")
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
display.set('status', 'Restarting wlan0 now!')
|
display.set('status', 'Restarting wlan0 now!')
|
||||||
@ -159,6 +180,25 @@ class FixServices(plugins.Plugin):
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||||
|
|
||||||
|
# Look for pattern 5
|
||||||
|
elif len(self.pattern5.findall(other_other_last_lines)) >= 1:
|
||||||
|
logging.debug("[Fix_Services] Bettercap has crashed!")
|
||||||
|
if hasattr(agent, 'view'):
|
||||||
|
display = agent.view()
|
||||||
|
display.set('status', 'Restarting pwnagotchi!')
|
||||||
|
display.update(force=True)
|
||||||
|
os.system("systemctl restart bettercap")
|
||||||
|
pwnagotchi.restart("AUTO")
|
||||||
|
|
||||||
|
# Look for pattern 6
|
||||||
|
elif len(self.pattern6.findall(other_other_last_lines)) >= 1:
|
||||||
|
logging.debug("[Fix_Services] Bettercap has crashed!")
|
||||||
|
if hasattr(agent, 'view'):
|
||||||
|
display = agent.view()
|
||||||
|
display.set('status', 'Restarting pwnagotchi!')
|
||||||
|
display.update(force=True)
|
||||||
|
os.system("systemctl restart bettercap")
|
||||||
|
pwnagotchi.restart("AUTO")
|
||||||
else:
|
else:
|
||||||
print("logs look good")
|
print("logs look good")
|
||||||
|
|
||||||
@ -171,7 +211,7 @@ class FixServices(plugins.Plugin):
|
|||||||
elif level == "debug":
|
elif level == "debug":
|
||||||
logging.debug(message)
|
logging.debug(message)
|
||||||
else:
|
else:
|
||||||
logging.info(message)
|
logging.debug(message)
|
||||||
|
|
||||||
if ui:
|
if ui:
|
||||||
ui.update(force=force, new_data=displayData)
|
ui.update(force=force, new_data=displayData)
|
||||||
@ -186,7 +226,7 @@ class FixServices(plugins.Plugin):
|
|||||||
# avoid overlapping restarts, but allow it if it's been a while
|
# avoid overlapping restarts, but allow it if it's been a while
|
||||||
# (in case the last attempt failed before resetting "isReloadingMon")
|
# (in case the last attempt failed before resetting "isReloadingMon")
|
||||||
if self.isReloadingMon and (time.time() - self.LASTTRY) < 180:
|
if self.isReloadingMon and (time.time() - self.LASTTRY) < 180:
|
||||||
logging.info("[Fix_Services] Duplicate attempt ignored")
|
logging.debug("[Fix_Services] Duplicate attempt ignored")
|
||||||
else:
|
else:
|
||||||
self.isReloadingMon = True
|
self.isReloadingMon = True
|
||||||
self.LASTTRY = time.time()
|
self.LASTTRY = time.time()
|
||||||
@ -213,7 +253,7 @@ class FixServices(plugins.Plugin):
|
|||||||
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
||||||
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||||
if ",UP," in str(cmd_output):
|
if ",UP," in str(cmd_output):
|
||||||
logging.info("wlan0mon is up. Skip reset?")
|
logging.debug("wlan0mon is up. Skip reset?")
|
||||||
# not reliable, so don't skip just yet
|
# not reliable, so don't skip just yet
|
||||||
# print("wlan0mon is up. Skipping reset.")
|
# print("wlan0mon is up. Skipping reset.")
|
||||||
# self.isReloadingMon = False
|
# self.isReloadingMon = False
|
||||||
@ -234,7 +274,7 @@ class FixServices(plugins.Plugin):
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services wifi.recon off] error %s" % (repr(err)))
|
logging.error("[Fix_Services wifi.recon off] error %s" % (repr(err)))
|
||||||
|
|
||||||
logging.info("[Fix_Services] recon paused. Now trying wlan0mon reload")
|
logging.debug("[Fix_Services] recon paused. Now trying wlan0mon reload")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output("monstop", shell=True)
|
cmd_output = subprocess.check_output("monstop", shell=True)
|
||||||
@ -250,14 +290,13 @@ class FixServices(plugins.Plugin):
|
|||||||
#
|
#
|
||||||
# Future: while "not fixed yet": blah blah blah. if "max_attemts", then reboot like the old days
|
# Future: while "not fixed yet": blah blah blah. if "max_attemts", then reboot like the old days
|
||||||
#
|
#
|
||||||
tries = 0
|
tries = 1
|
||||||
while tries < 3:
|
while tries < 3:
|
||||||
try:
|
try:
|
||||||
# unload the module
|
# unload the module
|
||||||
cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True)
|
cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True)
|
||||||
self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display,
|
self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display,
|
||||||
{"status": "Turning it off #%s" % tries, "face": faces.SMART})
|
{"status": "Turning it off #%s" % tries, "face": faces.SMART})
|
||||||
time.sleep(1 + tries)
|
|
||||||
|
|
||||||
# reload the module
|
# reload the module
|
||||||
try:
|
try:
|
||||||
@ -265,27 +304,25 @@ class FixServices(plugins.Plugin):
|
|||||||
cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True)
|
cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True)
|
||||||
|
|
||||||
self.logPrintView("info", "[Fix_Services] reloaded brcmfmac")
|
self.logPrintView("info", "[Fix_Services] reloaded brcmfmac")
|
||||||
time.sleep(10 + 4 * tries) # give it some time for wlan device to stabilize, or whatever
|
|
||||||
|
|
||||||
# success! now make the mon0
|
# success! now make the mon0
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||||
self.logPrintView("info", "[Fix_Services interface add wlan0mon] worked #%s: %s"
|
self.logPrintView("info", "[Fix_Services interface add wlan0mon worked #%s: %s"
|
||||||
% (tries, cmd_output))
|
% (tries, cmd_output))
|
||||||
time.sleep(tries + 5)
|
|
||||||
try:
|
try:
|
||||||
# try accessing mon0 in bettercap
|
# try accessing mon0 in bettercap
|
||||||
result = connection.run("set wifi.interface wlan0mon")
|
result = connection.run("set wifi.interface wlan0mon")
|
||||||
if "success" in result:
|
if "success" in result:
|
||||||
logging.info("[Fix_Services set wifi.interface wlan0mon] worked!")
|
logging.debug("[Fix_Services set wifi.interface wlan0mon worked!")
|
||||||
self._count = self._count + 1
|
|
||||||
time.sleep(1)
|
|
||||||
# stop looping and get back to recon
|
# stop looping and get back to recon
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logging.debug("[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
|
logging.debug(
|
||||||
|
"[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.debug("[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err))
|
logging.debug(
|
||||||
|
"[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err))
|
||||||
except Exception as cerr: #
|
except Exception as cerr: #
|
||||||
if not display:
|
if not display:
|
||||||
print("failed loading wlan0mon attempt #%s: %s" % (tries, repr(cerr)))
|
print("failed loading wlan0mon attempt #%s: %s" % (tries, repr(cerr)))
|
||||||
@ -303,11 +340,11 @@ class FixServices(plugins.Plugin):
|
|||||||
|
|
||||||
tries = tries + 1
|
tries = tries + 1
|
||||||
if tries < 3:
|
if tries < 3:
|
||||||
logging.info("[Fix_Services] wlan0mon didn't make it. trying again")
|
logging.debug("[Fix_Services] wlan0mon didn't make it. trying again")
|
||||||
if not display:
|
if not display:
|
||||||
print(" wlan0mon didn't make it. trying again")
|
print(" wlan0mon didn't make it. trying again")
|
||||||
else:
|
else:
|
||||||
logging.info("[Fix_Services] wlan0mon loading failed, no choice but to reboot ..")
|
logging.debug("[Fix_Services] wlan0mon loading failed, no choice but to reboot ..")
|
||||||
pwnagotchi.reboot()
|
pwnagotchi.reboot()
|
||||||
|
|
||||||
# exited the loop, so hopefully it loaded
|
# exited the loop, so hopefully it loaded
|
||||||
@ -317,14 +354,14 @@ class FixServices(plugins.Plugin):
|
|||||||
"face": faces.INTENSE})
|
"face": faces.INTENSE})
|
||||||
else:
|
else:
|
||||||
print("And back on again...")
|
print("And back on again...")
|
||||||
logging.info("[Fix_Services] wlan0mon back up")
|
logging.debug("[Fix_Services] wlan0mon back up")
|
||||||
else:
|
else:
|
||||||
self.LASTTRY = time.time()
|
self.LASTTRY = time.time()
|
||||||
|
|
||||||
time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap
|
time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap
|
||||||
self.isReloadingMon = False
|
self.isReloadingMon = False
|
||||||
|
|
||||||
logging.info("[Fix_Services] re-enable recon")
|
logging.debug("[Fix_Services] re-enable recon")
|
||||||
try:
|
try:
|
||||||
result = connection.run("wifi.clear; wifi.recon on")
|
result = connection.run("wifi.clear; wifi.recon on")
|
||||||
|
|
||||||
@ -345,13 +382,24 @@ class FixServices(plugins.Plugin):
|
|||||||
logging.error("[Fix_Services wifi.recon on] %s" % repr(err))
|
logging.error("[Fix_Services wifi.recon on] %s" % repr(err))
|
||||||
pwnagotchi.reboot()
|
pwnagotchi.reboot()
|
||||||
|
|
||||||
def on_unload(self, ui):
|
# called to setup the ui elements
|
||||||
|
def on_ui_setup(self, ui):
|
||||||
with ui._lock:
|
with ui._lock:
|
||||||
try:
|
# add custom UI elements
|
||||||
logging.info("[Fix_Services] unloaded")
|
if "position" in self.options:
|
||||||
except Exception as err:
|
pos = self.options['position'].split(',')
|
||||||
logging.error("[Fix_Services] unload err %s " % repr(err))
|
pos = [int(x.strip()) for x in pos]
|
||||||
pass
|
else:
|
||||||
|
pos = (ui.width() / 2 + 35, ui.height() - 11)
|
||||||
|
|
||||||
|
logging.debug("Got here")
|
||||||
|
|
||||||
|
# called when the ui is updated
|
||||||
|
def on_ui_update(self, ui):
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_unload(self, ui):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
# run from command line to brute force a reload
|
# run from command line to brute force a reload
|
||||||
|
@ -98,7 +98,7 @@ class GPS(plugins.Plugin):
|
|||||||
lat_pos = (127, 74)
|
lat_pos = (127, 74)
|
||||||
lon_pos = (122, 84)
|
lon_pos = (122, 84)
|
||||||
alt_pos = (127, 94)
|
alt_pos = (127, 94)
|
||||||
elif ui.is_waveshare27inch():
|
elif ui.is_waveshare2in7():
|
||||||
lat_pos = (6, 120)
|
lat_pos = (6, 120)
|
||||||
lon_pos = (1, 135)
|
lon_pos = (1, 135)
|
||||||
alt_pos = (6, 150)
|
alt_pos = (6, 150)
|
||||||
|
@ -5,7 +5,6 @@ import glob
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import pwnagotchi.grid as grid
|
import pwnagotchi.grid as grid
|
||||||
import pwnagotchi.plugins
|
|
||||||
import pwnagotchi.plugins as plugins
|
import pwnagotchi.plugins as plugins
|
||||||
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
|
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
@ -87,7 +86,7 @@ class Grid(plugins.Plugin):
|
|||||||
agent.view().on_unread_messages(self.unread_messages, self.total_messages)
|
agent.view().on_unread_messages(self.unread_messages, self.total_messages)
|
||||||
|
|
||||||
def check_handshakes(self, agent):
|
def check_handshakes(self, agent):
|
||||||
logging.debug("checking pcaps")
|
logging.debug("checking pcap's")
|
||||||
config = agent.config()
|
config = agent.config()
|
||||||
|
|
||||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||||
|
@ -1,179 +0,0 @@
|
|||||||
import logging
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import pwnagotchi.plugins as plugins
|
|
||||||
from threading import Lock
|
|
||||||
|
|
||||||
'''
|
|
||||||
hcxpcapngtool needed, to install:
|
|
||||||
> git clone https://github.com/ZerBea/hcxtools.git
|
|
||||||
> cd hcxtools
|
|
||||||
> apt-get install libcurl4-openssl-dev libssl-dev zlib1g-dev
|
|
||||||
> make
|
|
||||||
> sudo make install
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class Hashie(plugins.Plugin):
|
|
||||||
__author__ = 'Jayofelony'
|
|
||||||
__version__ = '1.0.4'
|
|
||||||
__license__ = 'GPL3'
|
|
||||||
__description__ = '''
|
|
||||||
Attempt to automatically convert pcaps to a crackable format.
|
|
||||||
If successful, the files containing the hashes will be saved
|
|
||||||
in the same folder as the handshakes.
|
|
||||||
The files are saved in their respective Hashcat format:
|
|
||||||
- EAPOL hashes are saved as *.22000
|
|
||||||
- PMKID hashes are saved as *.16800
|
|
||||||
All PCAP files without enough information to create a hash are
|
|
||||||
stored in a file that can be read by the webgpsmap plugin.
|
|
||||||
|
|
||||||
Why use it?:
|
|
||||||
- Automatically convert handshakes to crackable formats!
|
|
||||||
We dont all upload our hashes online ;)
|
|
||||||
- Repair PMKID handshakes that hcxpcapngtool misses
|
|
||||||
- If running at time of handshake capture, on_handshake can
|
|
||||||
be used to improve the chance of the repair succeeding
|
|
||||||
- Be a completionist! Not enough packets captured to crack a network?
|
|
||||||
This generates an output file for the webgpsmap plugin, use the
|
|
||||||
location data to revisit networks you need more packets for!
|
|
||||||
|
|
||||||
Additional information:
|
|
||||||
- Currently requires hcxpcapngtool compiled and installed
|
|
||||||
- Attempts to repair PMKID hashes when hcxpcapngtool cant find the SSID
|
|
||||||
- hcxpcapngtool sometimes has trouble extracting the SSID, so we
|
|
||||||
use the raw 16800 output and attempt to retrieve the SSID via tcpdump
|
|
||||||
- When access_point data is available (on_handshake), we leverage
|
|
||||||
the reported AP name and MAC to complete the hash
|
|
||||||
- The repair is very basic and could certainly be improved!
|
|
||||||
Todo:
|
|
||||||
Make it so users dont need hcxpcapngtool (unless it gets added to the base image)
|
|
||||||
Phase 1: Extract/construct 22000/16800 hashes through tcpdump commands
|
|
||||||
Phase 2: Extract/construct 22000/16800 hashes entirely in python
|
|
||||||
Improve the code, a lot
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.lock = Lock()
|
|
||||||
self.options = dict()
|
|
||||||
|
|
||||||
def on_loaded(self):
|
|
||||||
logging.info("[Hashie] Plugin loaded")
|
|
||||||
|
|
||||||
def on_unloaded(self):
|
|
||||||
logging.info("[Hashie] Plugin unloaded")
|
|
||||||
|
|
||||||
# called when everything is ready and the main loop is about to start
|
|
||||||
def on_ready(self, agent):
|
|
||||||
config = agent.config()
|
|
||||||
handshake_dir = config['bettercap']['handshakes']
|
|
||||||
|
|
||||||
logging.info('[Hashie] Starting batch conversion of pcap files')
|
|
||||||
with self.lock:
|
|
||||||
self._process_stale_pcaps(handshake_dir)
|
|
||||||
|
|
||||||
def on_handshake(self, agent, filename, access_point, client_station):
|
|
||||||
with self.lock:
|
|
||||||
handshake_status = []
|
|
||||||
fullpathNoExt = filename.split('.')[0]
|
|
||||||
name = filename.split('/')[-1:][0].split('.')[0]
|
|
||||||
|
|
||||||
if os.path.isfile(fullpathNoExt + '.22000'):
|
|
||||||
handshake_status.append('Already have {}.22000 (EAPOL)'.format(name))
|
|
||||||
elif self._writeEAPOL(filename):
|
|
||||||
handshake_status.append('Created {}.22000 (EAPOL) from pcap'.format(name))
|
|
||||||
|
|
||||||
if os.path.isfile(fullpathNoExt + '.16800'):
|
|
||||||
handshake_status.append('Already have {}.16800 (PMKID)'.format(name))
|
|
||||||
elif self._writePMKID(filename):
|
|
||||||
handshake_status.append('Created {}.16800 (PMKID) from pcap'.format(name))
|
|
||||||
|
|
||||||
if handshake_status:
|
|
||||||
logging.info('[Hashie] Good news:\n\t' + '\n\t'.join(handshake_status))
|
|
||||||
|
|
||||||
def _writeEAPOL(self, fullpath):
|
|
||||||
fullpathNoExt = fullpath.split('.')[0]
|
|
||||||
filename = fullpath.split('/')[-1:][0].split('.')[0]
|
|
||||||
subprocess.getoutput('hcxpcapngtool -o {}.22000 {} >/dev/null 2>&1'.format(fullpathNoExt, fullpath))
|
|
||||||
if os.path.isfile(fullpathNoExt + '.22000'):
|
|
||||||
logging.debug('[Hashie] [+] EAPOL Success: {}.22000 created'.format(filename))
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _writePMKID(self, fullpath):
|
|
||||||
fullpathNoExt = fullpath.split('.')[0]
|
|
||||||
filename = fullpath.split('/')[-1:][0].split('.')[0]
|
|
||||||
subprocess.getoutput('hcxpcapngtool -o {}.16800 {} >/dev/null 2>&1'.format(fullpathNoExt, fullpath))
|
|
||||||
if os.path.isfile(fullpathNoExt + '.16800'):
|
|
||||||
logging.debug('[Hashie] [+] PMKID Success: {}.16800 created'.format(filename))
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _process_stale_pcaps(self, handshake_dir):
|
|
||||||
handshakes_list = [os.path.join(handshake_dir, filename) for filename in os.listdir(handshake_dir) if filename.endswith('.pcap')]
|
|
||||||
failed_jobs = []
|
|
||||||
successful_jobs = []
|
|
||||||
lonely_pcaps = []
|
|
||||||
for num, handshake in enumerate(handshakes_list):
|
|
||||||
fullpathNoExt = handshake.split('.')[0]
|
|
||||||
pcapFileName = handshake.split('/')[-1:][0]
|
|
||||||
if not os.path.isfile(fullpathNoExt + '.22000'): # if no 22000, try
|
|
||||||
if self._writeEAPOL(handshake):
|
|
||||||
successful_jobs.append('22000: ' + pcapFileName)
|
|
||||||
else:
|
|
||||||
failed_jobs.append('22000: ' + pcapFileName)
|
|
||||||
if not os.path.isfile(fullpathNoExt + '.16800'): # if no 16800, try
|
|
||||||
if self._writePMKID(handshake):
|
|
||||||
successful_jobs.append('16800: ' + pcapFileName)
|
|
||||||
else:
|
|
||||||
failed_jobs.append('16800: ' + pcapFileName)
|
|
||||||
if not os.path.isfile(fullpathNoExt + '.22000'): # if no 16800 AND no 22000
|
|
||||||
lonely_pcaps.append(handshake)
|
|
||||||
logging.debug('[hashie] Batch job: added {} to lonely list'.format(pcapFileName))
|
|
||||||
if ((num + 1) % 50 == 0) or (num + 1 == len(handshakes_list)): # report progress every 50, or when done
|
|
||||||
logging.info('[Hashie] Batch job: {}/{} done ({} fails)'.format(num + 1, len(handshakes_list), len(lonely_pcaps)))
|
|
||||||
if successful_jobs:
|
|
||||||
logging.info('[Hashie] Batch job: {} new handshake files created'.format(len(successful_jobs)))
|
|
||||||
if lonely_pcaps:
|
|
||||||
logging.info('[Hashie] Batch job: {} networks without enough packets to create a hash'.format(len(lonely_pcaps)))
|
|
||||||
self._getLocations(lonely_pcaps)
|
|
||||||
|
|
||||||
def _getLocations(self, lonely_pcaps):
|
|
||||||
# export a file for webgpsmap to load
|
|
||||||
with open('/root/.incompletePcaps', 'w') as isIncomplete:
|
|
||||||
count = 0
|
|
||||||
for pcapFile in lonely_pcaps:
|
|
||||||
filename = pcapFile.split('/')[-1:][0] # keep extension
|
|
||||||
fullpathNoExt = pcapFile.split('.')[0]
|
|
||||||
isIncomplete.write(filename + '\n')
|
|
||||||
if os.path.isfile(fullpathNoExt + '.gps.json') or os.path.isfile(fullpathNoExt + '.geo.json'):
|
|
||||||
count += 1
|
|
||||||
if count != 0:
|
|
||||||
logging.info('[Hashie] Used {} GPS/GEO files to find lonely networks, '
|
|
||||||
'go check webgpsmap! ;)'.format(str(count)))
|
|
||||||
else:
|
|
||||||
logging.info('[Hashie] Could not find any GPS/GEO files '
|
|
||||||
'for the lonely networks'.format(str(count)))
|
|
||||||
|
|
||||||
def _getLocationsCSV(self, lonely_pcaps):
|
|
||||||
# in case we need this later, export locations manually to CSV file, needs try/catch format/etc.
|
|
||||||
locations = []
|
|
||||||
for pcapFile in lonely_pcaps:
|
|
||||||
filename = pcapFile.split('/')[-1:][0].split('.')[0]
|
|
||||||
fullpathNoExt = pcapFile.split('.')[0]
|
|
||||||
if os.path.isfile(fullpathNoExt + '.gps.json'):
|
|
||||||
with open(fullpathNoExt + '.gps.json', 'r') as tempFileA:
|
|
||||||
data = json.load(tempFileA)
|
|
||||||
locations.append(filename + ',' + str(data['Latitude']) + ',' + str(data['Longitude']) + ',50')
|
|
||||||
elif os.path.isfile(fullpathNoExt + '.geo.json'):
|
|
||||||
with open(fullpathNoExt + '.geo.json', 'r') as tempFileB:
|
|
||||||
data = json.load(tempFileB)
|
|
||||||
locations.append(
|
|
||||||
filename + ',' + str(data['location']['lat']) + ',' + str(data['location']['lng']) + ',' + str(data['accuracy']))
|
|
||||||
if locations:
|
|
||||||
with open('/root/locations.csv', 'w') as tempFileD:
|
|
||||||
for loc in locations:
|
|
||||||
tempFileD.write(loc + '\n')
|
|
||||||
logging.info('[Hashie] Used {} GPS/GEO files to find lonely networks, '
|
|
||||||
'load /root/locations.csv into a mapping app and go say hi!'.format(len(locations)))
|
|
@ -253,7 +253,6 @@ class Logtail(plugins.Plugin):
|
|||||||
"""
|
"""
|
||||||
logging.info("Logtail plugin loaded.")
|
logging.info("Logtail plugin loaded.")
|
||||||
|
|
||||||
|
|
||||||
def on_webhook(self, path, request):
|
def on_webhook(self, path, request):
|
||||||
if not self.ready:
|
if not self.ready:
|
||||||
return "Plugin not ready"
|
return "Plugin not ready"
|
||||||
|
@ -144,6 +144,9 @@ class MemTemp(plugins.Plugin):
|
|||||||
elif ui.is_waveshare2in7():
|
elif ui.is_waveshare2in7():
|
||||||
h_pos = (192, 138)
|
h_pos = (192, 138)
|
||||||
v_pos = (211, 122)
|
v_pos = (211, 122)
|
||||||
|
elif ui.is_waveshare1in54V2():
|
||||||
|
h_pos = (53, 77)
|
||||||
|
v_pos = (154, 65)
|
||||||
else:
|
else:
|
||||||
h_pos = (155, 76)
|
h_pos = (155, 76)
|
||||||
v_pos = (175, 61)
|
v_pos = (175, 61)
|
||||||
|
@ -142,6 +142,6 @@ class OnlineHashCrack(plugins.Plugin):
|
|||||||
for row in csv.DictReader(cracked_list):
|
for row in csv.DictReader(cracked_list):
|
||||||
if row['password']:
|
if row['password']:
|
||||||
filename = re.sub(r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace(':','')
|
filename = re.sub(r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace(':','')
|
||||||
if os.path.exists( os.path.join(handshake_dir, filename+'.pcap') ):
|
if os.path.exists( os.path.join(handshake_dir, filename+'.pcap')):
|
||||||
with open(os.path.join(handshake_dir, filename+'.pcap.cracked'), 'w') as f:
|
with open(os.path.join(handshake_dir, filename+'.pcap.cracked'), 'w') as f:
|
||||||
f.write(row['password'])
|
f.write(row['password'])
|
||||||
|
@ -14,7 +14,7 @@ from dateutil.parser import parse
|
|||||||
|
|
||||||
the plugin does the following:
|
the plugin does the following:
|
||||||
- search for *.pcap files in your /handshakes/ dir
|
- search for *.pcap files in your /handshakes/ dir
|
||||||
- for every found .pcap file it looks for a .geo.json or .gps.json or .paw-gps.json file with
|
- for every found .pcap file it looks for a .geo.json or .gps.json or file with
|
||||||
latitude+longitude data inside and shows this position on the map
|
latitude+longitude data inside and shows this position on the map
|
||||||
- if also an .cracked file with a plaintext password inside exist, it reads the content and shows the
|
- if also an .cracked file with a plaintext password inside exist, it reads the content and shows the
|
||||||
position as green instead of red and the password inside the infopox of the position
|
position as green instead of red and the password inside the infopox of the position
|
||||||
@ -87,7 +87,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
# returns all positions
|
# returns all positions
|
||||||
try:
|
try:
|
||||||
self.ALREADY_SENT = list()
|
self.ALREADY_SENT = list()
|
||||||
response_data = bytes(json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes'])), "utf-8")
|
response_data = bytes(
|
||||||
|
json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes'])), "utf-8")
|
||||||
response_status = 200
|
response_status = 200
|
||||||
response_mimetype = "application/json"
|
response_mimetype = "application/json"
|
||||||
response_header_contenttype = 'application/json'
|
response_header_contenttype = 'application/json'
|
||||||
@ -100,7 +101,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
self.ALREADY_SENT = list()
|
self.ALREADY_SENT = list()
|
||||||
json_data = json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']))
|
json_data = json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']))
|
||||||
html_data = self.get_html()
|
html_data = self.get_html()
|
||||||
html_data = html_data.replace('var positions = [];', 'var positions = ' + json_data + ';positionsLoaded=true;drawPositions();')
|
html_data = html_data.replace('var positions = [];',
|
||||||
|
'var positions = ' + json_data + ';positionsLoaded=true;drawPositions();')
|
||||||
response_data = bytes(html_data, "utf-8")
|
response_data = bytes(html_data, "utf-8")
|
||||||
response_status = 200
|
response_status = 200
|
||||||
response_mimetype = "application/xhtml+xml"
|
response_mimetype = "application/xhtml+xml"
|
||||||
@ -163,7 +165,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
|
|
||||||
all_files = os.listdir(handshake_dir)
|
all_files = os.listdir(handshake_dir)
|
||||||
# print(all_files)
|
# print(all_files)
|
||||||
all_pcap_files = [os.path.join(handshake_dir, filename) for filename in all_files if filename.endswith('.pcap')]
|
all_pcap_files = [os.path.join(handshake_dir, filename) for filename in all_files if
|
||||||
|
filename.endswith('.pcap')]
|
||||||
all_geo_or_gps_files = []
|
all_geo_or_gps_files = []
|
||||||
for filename_pcap in all_pcap_files:
|
for filename_pcap in all_pcap_files:
|
||||||
filename_base = filename_pcap[:-5] # remove ".pcap"
|
filename_base = filename_pcap[:-5] # remove ".pcap"
|
||||||
@ -180,22 +183,18 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
if check_for in all_files:
|
if check_for in all_files:
|
||||||
filename_position = str(os.path.join(handshake_dir, check_for))
|
filename_position = str(os.path.join(handshake_dir, check_for))
|
||||||
|
|
||||||
logging.debug("[webgpsmap] search for .paw-gps.json")
|
|
||||||
check_for = os.path.basename(filename_base) + ".paw-gps.json"
|
|
||||||
if check_for in all_files:
|
|
||||||
filename_position = str(os.path.join(handshake_dir, check_for))
|
|
||||||
|
|
||||||
logging.debug(f"[webgpsmap] end search for position data files and use {filename_position}")
|
logging.debug(f"[webgpsmap] end search for position data files and use {filename_position}")
|
||||||
|
|
||||||
if filename_position is not None:
|
if filename_position is not None:
|
||||||
all_geo_or_gps_files.append(filename_position)
|
all_geo_or_gps_files.append(filename_position)
|
||||||
|
|
||||||
# all_geo_or_gps_files = set(all_geo_or_gps_files) - set(SKIP) # remove skipped networks? No!
|
# all_geo_or_gps_files = set(all_geo_or_gps_files) - set(SKIP) # remove skipped networks? No!
|
||||||
|
|
||||||
if newest_only:
|
if newest_only:
|
||||||
all_geo_or_gps_files = set(all_geo_or_gps_files) - set(self.ALREADY_SENT)
|
all_geo_or_gps_files = set(all_geo_or_gps_files) - set(self.ALREADY_SENT)
|
||||||
|
|
||||||
logging.info(f"[webgpsmap] Found {len(all_geo_or_gps_files)} position-data files from {len(all_pcap_files)} handshakes. Fetching positions ...")
|
logging.info(
|
||||||
|
f"[webgpsmap] Found {len(all_geo_or_gps_files)} position-data files from {len(all_pcap_files)} handshakes. Fetching positions ...")
|
||||||
|
|
||||||
for pos_file in all_geo_or_gps_files:
|
for pos_file in all_geo_or_gps_files:
|
||||||
try:
|
try:
|
||||||
@ -213,9 +212,7 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
pos_type = 'gps'
|
pos_type = 'gps'
|
||||||
elif pos.type() == PositionFile.GEO:
|
elif pos.type() == PositionFile.GEO:
|
||||||
pos_type = 'geo'
|
pos_type = 'geo'
|
||||||
elif pos.type() == PositionFile.PAWGPS:
|
gps_data[ssid + "_" + mac] = {
|
||||||
pos_type = 'paw'
|
|
||||||
gps_data[ssid+"_"+mac] = {
|
|
||||||
'ssid': ssid,
|
'ssid': ssid,
|
||||||
'mac': mac,
|
'mac': mac,
|
||||||
'type': pos_type,
|
'type': pos_type,
|
||||||
@ -224,7 +221,7 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
'acc': pos.accuracy(),
|
'acc': pos.accuracy(),
|
||||||
'ts_first': pos.timestamp_first(),
|
'ts_first': pos.timestamp_first(),
|
||||||
'ts_last': pos.timestamp_last(),
|
'ts_last': pos.timestamp_last(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# get ap password if exist
|
# get ap password if exist
|
||||||
check_for = os.path.basename(pos_file).split(".")[0] + ".pcap.cracked"
|
check_for = os.path.basename(pos_file).split(".")[0] + ".pcap.cracked"
|
||||||
@ -265,7 +262,6 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
GPS = 1
|
GPS = 1
|
||||||
GEO = 2
|
GEO = 2
|
||||||
PAWGPS = 3
|
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self._file = path
|
self._file = path
|
||||||
@ -282,7 +278,7 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
Returns the mac from filename
|
Returns the mac from filename
|
||||||
"""
|
"""
|
||||||
parsed_mac = re.search(r'.*_?([a-zA-Z0-9]{12})\.(?:gps|geo|paw-gps)\.json', self._filename)
|
parsed_mac = re.search(r'.*_?([a-zA-Z0-9]{12})\.(?:gps|geo)\.json', self._filename)
|
||||||
if parsed_mac:
|
if parsed_mac:
|
||||||
mac = parsed_mac.groups()[0]
|
mac = parsed_mac.groups()[0]
|
||||||
return mac
|
return mac
|
||||||
@ -292,7 +288,7 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
Returns the ssid from filename
|
Returns the ssid from filename
|
||||||
"""
|
"""
|
||||||
parsed_ssid = re.search(r'(.+)_[a-zA-Z0-9]{12}\.(?:gps|geo|paw-gps)\.json', self._filename)
|
parsed_ssid = re.search(r'(.+)_[a-zA-Z0-9]{12}\.(?:gps|geo)\.json', self._filename)
|
||||||
if parsed_ssid:
|
if parsed_ssid:
|
||||||
return parsed_ssid.groups()[0]
|
return parsed_ssid.groups()[0]
|
||||||
return None
|
return None
|
||||||
@ -354,8 +350,6 @@ class PositionFile:
|
|||||||
return PositionFile.GPS
|
return PositionFile.GPS
|
||||||
if self._file.endswith('.geo.json'):
|
if self._file.endswith('.geo.json'):
|
||||||
return PositionFile.GEO
|
return PositionFile.GEO
|
||||||
if self._file.endswith('.paw-gps.json'):
|
|
||||||
return PositionFile.PAWGPS
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def lat(self):
|
def lat(self):
|
||||||
@ -402,9 +396,7 @@ class PositionFile:
|
|||||||
|
|
||||||
def accuracy(self):
|
def accuracy(self):
|
||||||
if self.type() == PositionFile.GPS:
|
if self.type() == PositionFile.GPS:
|
||||||
return 50.0 # a default
|
return 50.0 # a default
|
||||||
if self.type() == PositionFile.PAWGPS:
|
|
||||||
return 50.0 # a default
|
|
||||||
if self.type() == PositionFile.GEO:
|
if self.type() == PositionFile.GEO:
|
||||||
try:
|
try:
|
||||||
return self._json['accuracy']
|
return self._json['accuracy']
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from PIL import Image
|
from PIL import Image, ImageOps
|
||||||
from textwrap import TextWrapper
|
from textwrap import TextWrapper
|
||||||
|
|
||||||
|
|
||||||
@ -40,21 +40,37 @@ class FilledRect(Widget):
|
|||||||
|
|
||||||
|
|
||||||
class Text(Widget):
|
class Text(Widget):
|
||||||
def __init__(self, value="", position=(0, 0), font=None, color=0, wrap=False, max_length=0):
|
def __init__(self, value="", position=(0, 0), font=None, color=0, wrap=False, max_length=0, png=False):
|
||||||
super().__init__(position, color)
|
super().__init__(position, color)
|
||||||
self.value = value
|
self.value = value
|
||||||
self.font = font
|
self.font = font
|
||||||
self.wrap = wrap
|
self.wrap = wrap
|
||||||
self.max_length = max_length
|
self.max_length = max_length
|
||||||
self.wrapper = TextWrapper(width=self.max_length, replace_whitespace=False) if wrap else None
|
self.wrapper = TextWrapper(width=self.max_length, replace_whitespace=False) if wrap else None
|
||||||
|
self.png = png
|
||||||
|
|
||||||
def draw(self, canvas, drawer):
|
def draw(self, canvas, drawer):
|
||||||
if self.value is not None:
|
if self.value is not None:
|
||||||
if self.wrap:
|
if not self.png:
|
||||||
text = '\n'.join(self.wrapper.wrap(self.value))
|
if self.wrap:
|
||||||
|
text = '\n'.join(self.wrapper.wrap(self.value))
|
||||||
|
else:
|
||||||
|
text = self.value
|
||||||
|
drawer.text(self.xy, text, font=self.font, fill=self.color)
|
||||||
else:
|
else:
|
||||||
text = self.value
|
self.image = Image.open(self.value)
|
||||||
drawer.text(self.xy, text, font=self.font, fill=self.color)
|
self.image = self.image.convert('RGBA')
|
||||||
|
self.pixels = self.image.load()
|
||||||
|
for y in range(self.image.size[1]):
|
||||||
|
for x in range(self.image.size[0]):
|
||||||
|
if self.pixels[x,y][3] < 255: # check alpha
|
||||||
|
self.pixels[x,y] = (255, 255, 255, 255)
|
||||||
|
if self.color == 255:
|
||||||
|
self._image = ImageOps.colorize(self.image.convert('L'), black = "white", white = "black")
|
||||||
|
else:
|
||||||
|
self._image = self.image
|
||||||
|
self.image = self._image.convert('1')
|
||||||
|
canvas.paste(self.image, self.xy)
|
||||||
|
|
||||||
|
|
||||||
class LabeledValue(Widget):
|
class LabeledValue(Widget):
|
||||||
|
@ -28,6 +28,39 @@ class Display(View):
|
|||||||
def is_lcdhat(self):
|
def is_lcdhat(self):
|
||||||
return self._implementation.name == 'lcdhat'
|
return self._implementation.name == 'lcdhat'
|
||||||
|
|
||||||
|
def is_wavesharelcd0in96(self):
|
||||||
|
return self._implementation.name == 'wavesharelcd0in96'
|
||||||
|
|
||||||
|
def is_wavesharelcd1in3(self):
|
||||||
|
return self._implementation.name == 'wavesharelcd1in3'
|
||||||
|
|
||||||
|
def is_wavesharelcd1in8(self):
|
||||||
|
return self._implementation.name == 'wavesharelcd1in8'
|
||||||
|
|
||||||
|
def is_wavesharelcd1in9(self):
|
||||||
|
return self._implementation.name == 'wavesharelcd1in9'
|
||||||
|
|
||||||
|
def is_wavesharelcd1in14(self):
|
||||||
|
return self._implementation.name == 'wavesharelcd1in14'
|
||||||
|
|
||||||
|
def is_wavesharelcd1in28(self):
|
||||||
|
return self._implementation.name == 'wavesharelcd1in28'
|
||||||
|
|
||||||
|
def is_wavesharelcd1in47(self):
|
||||||
|
return self._implementation.name == 'wavesharelcd1in47'
|
||||||
|
|
||||||
|
def is_wavesharelcd1in54(self):
|
||||||
|
return self._implementation.name == 'wavesharelcd1in54'
|
||||||
|
|
||||||
|
def is_wavesharelcd1in69(self):
|
||||||
|
return self._implementation.name == 'wavesharelcd1in69'
|
||||||
|
|
||||||
|
def is_wavesharelcd2in0(self):
|
||||||
|
return self._implementation.name == 'wavesharelcd2in0'
|
||||||
|
|
||||||
|
def is_wavesharelcd2in4(self):
|
||||||
|
return self._implementation.name == 'wavesharelcd2in4'
|
||||||
|
|
||||||
def is_waveshare144lcd(self):
|
def is_waveshare144lcd(self):
|
||||||
return self._implementation.name == 'waveshare144lcd'
|
return self._implementation.name == 'waveshare144lcd'
|
||||||
|
|
||||||
@ -118,6 +151,9 @@ class Display(View):
|
|||||||
def is_waveshare2in66g(self):
|
def is_waveshare2in66g(self):
|
||||||
return self._implementation.name == 'waveshare2in66g'
|
return self._implementation.name == 'waveshare2in66g'
|
||||||
|
|
||||||
|
def is_weact2in9(self):
|
||||||
|
return self._implementation.name == 'weact2in9'
|
||||||
|
|
||||||
def is_waveshare3in0g(self):
|
def is_waveshare3in0g(self):
|
||||||
return self._implementation.name == 'waveshare3in0g'
|
return self._implementation.name == 'waveshare3in0g'
|
||||||
|
|
||||||
@ -151,6 +187,12 @@ class Display(View):
|
|||||||
def is_waveshare5in65f(self):
|
def is_waveshare5in65f(self):
|
||||||
return self._implementation.name == 'waveshare5in65f'
|
return self._implementation.name == 'waveshare5in65f'
|
||||||
|
|
||||||
|
def is_waveshare5in79(self):
|
||||||
|
return self._implementation.name == 'waveshare5in79'
|
||||||
|
|
||||||
|
def is_waveshare5in79b(self):
|
||||||
|
return self._implementation.name == 'waveshare5in79b'
|
||||||
|
|
||||||
def is_waveshare5in83(self):
|
def is_waveshare5in83(self):
|
||||||
return self._implementation.name == 'waveshare5in83'
|
return self._implementation.name == 'waveshare5in83'
|
||||||
|
|
||||||
@ -193,6 +235,9 @@ class Display(View):
|
|||||||
def is_inky(self):
|
def is_inky(self):
|
||||||
return self._implementation.name == 'inky'
|
return self._implementation.name == 'inky'
|
||||||
|
|
||||||
|
def is_dummy_display(self):
|
||||||
|
return self._implementation.name == 'dummydisplay'
|
||||||
|
|
||||||
def is_papirus(self):
|
def is_papirus(self):
|
||||||
return self._implementation.name == 'papirus'
|
return self._implementation.name == 'papirus'
|
||||||
|
|
||||||
@ -208,9 +253,24 @@ class Display(View):
|
|||||||
def is_displayhatmini(self):
|
def is_displayhatmini(self):
|
||||||
return self._implementation.name == 'displayhatmini'
|
return self._implementation.name == 'displayhatmini'
|
||||||
|
|
||||||
|
def is_pirateaudio(self):
|
||||||
|
return self._implementation.name == 'pirateaudio'
|
||||||
|
|
||||||
|
def is_pitft(self):
|
||||||
|
return self._implementation.name == 'pitft'
|
||||||
|
|
||||||
|
def is_tftbonnet(self):
|
||||||
|
return self._implementation.name == 'tftbonnet'
|
||||||
|
|
||||||
|
def is_waveshareoledlcd(self):
|
||||||
|
return self._implementation.name == 'waveshareoledlcd'
|
||||||
|
|
||||||
def is_waveshare35lcd(self):
|
def is_waveshare35lcd(self):
|
||||||
return self._implementation.name == 'waveshare35lcd'
|
return self._implementation.name == 'waveshare35lcd'
|
||||||
|
|
||||||
|
def is_adfruit213v3(self):
|
||||||
|
return self._implementation.name == 'adafruit2in13_v3'
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ DEBUG = '(#__#)'
|
|||||||
UPLOAD = '(1__0)'
|
UPLOAD = '(1__0)'
|
||||||
UPLOAD1 = '(1__1)'
|
UPLOAD1 = '(1__1)'
|
||||||
UPLOAD2 = '(0__1)'
|
UPLOAD2 = '(0__1)'
|
||||||
|
PNG = False
|
||||||
|
POSITION_X = 0
|
||||||
|
POSITION_Y = 40
|
||||||
|
|
||||||
|
|
||||||
def load_from_config(config):
|
def load_from_config(config):
|
||||||
|
@ -1,259 +1,333 @@
|
|||||||
from pwnagotchi.ui.hw.inky import Inky
|
|
||||||
from pwnagotchi.ui.hw.papirus import Papirus
|
|
||||||
from pwnagotchi.ui.hw.oledhat import OledHat
|
|
||||||
from pwnagotchi.ui.hw.lcdhat import LcdHat
|
|
||||||
from pwnagotchi.ui.hw.dfrobot import DFRobotV1
|
|
||||||
from pwnagotchi.ui.hw.dfrobot_v2 import DFRobotV2
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in13 import WaveshareV1
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in13_V2 import WaveshareV2
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in13_V3 import WaveshareV3
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in13_V4 import WaveshareV4
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in7 import Waveshare27inch
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in7_V2 import Waveshare27inchV2
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in9 import Waveshare29inch
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in9_V2 import Waveshare29inchV2
|
|
||||||
from pwnagotchi.ui.hw.waveshare1in44lcd import Waveshare144lcd
|
|
||||||
from pwnagotchi.ui.hw.waveshare1in54b import Waveshare154inchb
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in13bc import Waveshare213bc
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in13d import Waveshare213d
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in13g import Waveshare2in13g
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in13b_V4 import Waveshare213bV4
|
|
||||||
from pwnagotchi.ui.hw.waveshare3in5lcd import Waveshare35lcd
|
|
||||||
from pwnagotchi.ui.hw.spotpear24in import Spotpear24inch
|
|
||||||
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
|
|
||||||
from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02
|
|
||||||
from pwnagotchi.ui.hw.waveshare1in54 import Waveshare154
|
|
||||||
from pwnagotchi.ui.hw.waveshare1in54_V2 import Waveshare154V2
|
|
||||||
from pwnagotchi.ui.hw.waveshare1in54b_V2 import Waveshare154bV2
|
|
||||||
from pwnagotchi.ui.hw.waveshare1in54c import Waveshare1in54c
|
|
||||||
from pwnagotchi.ui.hw.waveshare1in64g import Waveshare1in64g
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in7b import Waveshare27b
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in7b_V2 import Waveshare27bV2
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in9b_V3 import Waveshare29bV3
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in9b_V4 import Waveshare29bV4
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in9bc import Waveshare2in9bc
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in9d import Waveshare2in9d
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in13b_V3 import Waveshare2in13bV3
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in36g import Waveshare2in36g
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in66 import Waveshare2in66
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in66b import Waveshare2in66b
|
|
||||||
from pwnagotchi.ui.hw.waveshare2in66g import Waveshare2in66g
|
|
||||||
from pwnagotchi.ui.hw.waveshare3in0g import Waveshare3in0g
|
|
||||||
from pwnagotchi.ui.hw.waveshare3in7 import Waveshare3in7
|
|
||||||
from pwnagotchi.ui.hw.waveshare3in52 import Waveshare3in52
|
|
||||||
from pwnagotchi.ui.hw.waveshare4in01f import Waveshare4in01f
|
|
||||||
from pwnagotchi.ui.hw.waveshare4in2 import Waveshare4in2
|
|
||||||
from pwnagotchi.ui.hw.waveshare4in2_V2 import Waveshare4in2V2
|
|
||||||
from pwnagotchi.ui.hw.waveshare4in2b_V2 import Waveshare4in2bV2
|
|
||||||
from pwnagotchi.ui.hw.waveshare4in2bc import Waveshare4in2bc
|
|
||||||
from pwnagotchi.ui.hw.waveshare4in26 import Waveshare4in26
|
|
||||||
from pwnagotchi.ui.hw.waveshare4in37g import Waveshare4in37g
|
|
||||||
from pwnagotchi.ui.hw.waveshare5in65f import Waveshare5in65f
|
|
||||||
from pwnagotchi.ui.hw.waveshare5in83 import Waveshare5in83
|
|
||||||
from pwnagotchi.ui.hw.waveshare5in83_V2 import Waveshare5in83V2
|
|
||||||
from pwnagotchi.ui.hw.waveshare5in83b_V2 import Waveshare5in83bV2
|
|
||||||
from pwnagotchi.ui.hw.waveshare5in83bc import Waveshare5in83bc
|
|
||||||
from pwnagotchi.ui.hw.waveshare7in3f import Waveshare7in3f
|
|
||||||
from pwnagotchi.ui.hw.waveshare7in3g import Waveshare7in3g
|
|
||||||
from pwnagotchi.ui.hw.waveshare7in5 import Waveshare7in5
|
|
||||||
from pwnagotchi.ui.hw.waveshare7in5_HD import Waveshare7in5HD
|
|
||||||
from pwnagotchi.ui.hw.waveshare7in5_V2 import Waveshare7in5V2
|
|
||||||
from pwnagotchi.ui.hw.waveshare7in5b_HD import Waveshare7in5bHD
|
|
||||||
from pwnagotchi.ui.hw.waveshare7in5b_V2 import Waveshare7in5bV2
|
|
||||||
from pwnagotchi.ui.hw.waveshare7in5bc import Waveshare7in5bc
|
|
||||||
from pwnagotchi.ui.hw.waveshare13in3k import Waveshare13in3k
|
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
if config['ui']['display']['type'] == 'inky':
|
if config['ui']['display']['type'] == 'inky':
|
||||||
|
from pwnagotchi.ui.hw.inky import Inky
|
||||||
return Inky(config)
|
return Inky(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'wavesharelcd0in96':
|
||||||
|
from pwnagotchi.ui.hw.wavesharelcd0in96 import Wavesharelcd0in96
|
||||||
|
return Wavesharelcd0in96(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'wavesharelcd1in3':
|
||||||
|
from pwnagotchi.ui.hw.wavesharelcd1in3 import Wavesharelcd1in3
|
||||||
|
return Wavesharelcd1in3(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'wavesharelcd1in8':
|
||||||
|
from pwnagotchi.ui.hw.wavesharelcd1in8 import Wavesharelcd1in8
|
||||||
|
return Wavesharelcd1in8(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'wavesharelcd1in9':
|
||||||
|
from pwnagotchi.ui.hw.wavesharelcd1in9 import Wavesharelcd1in9
|
||||||
|
return Wavesharelcd1in9(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'wavesharelcd1in14':
|
||||||
|
from pwnagotchi.ui.hw.wavesharelcd1in14 import Wavesharelcd1in14
|
||||||
|
return Wavesharelcd1in14(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'wavesharelcd1in28':
|
||||||
|
from pwnagotchi.ui.hw.wavesharelcd1in28 import Wavesharelcd1in28
|
||||||
|
return Wavesharelcd1in28(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'wavesharelcd1in47':
|
||||||
|
from pwnagotchi.ui.hw.wavesharelcd1in47 import Wavesharelcd1in47
|
||||||
|
return Wavesharelcd1in47(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'wavesharelcd1in54':
|
||||||
|
from pwnagotchi.ui.hw.wavesharelcd1in54 import Wavesharelcd1in54
|
||||||
|
return Wavesharelcd1in54(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'wavesharelcd1in69':
|
||||||
|
from pwnagotchi.ui.hw.wavesharelcd1in69 import Wavesharelcd1in69
|
||||||
|
return Wavesharelcd1in69(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'wavesharelcd2in0':
|
||||||
|
from pwnagotchi.ui.hw.wavesharelcd2in0 import Wavesharelcd2in0
|
||||||
|
return Wavesharelcd2in0(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'wavesharelcd2in4':
|
||||||
|
from pwnagotchi.ui.hw.wavesharelcd2in4 import Wavesharelcd2in4
|
||||||
|
return Wavesharelcd2in4(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'dummydisplay':
|
||||||
|
from pwnagotchi.ui.hw.dummydisplay import DummyDisplay
|
||||||
|
return DummyDisplay(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'papirus':
|
elif config['ui']['display']['type'] == 'papirus':
|
||||||
|
from pwnagotchi.ui.hw.papirus import Papirus
|
||||||
return Papirus(config)
|
return Papirus(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'oledhat':
|
elif config['ui']['display']['type'] == 'oledhat':
|
||||||
|
from pwnagotchi.ui.hw.oledhat import OledHat
|
||||||
return OledHat(config)
|
return OledHat(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'lcdhat':
|
elif config['ui']['display']['type'] == 'lcdhat':
|
||||||
|
from pwnagotchi.ui.hw.lcdhat import LcdHat
|
||||||
return LcdHat(config)
|
return LcdHat(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'dfrobot_1':
|
elif config['ui']['display']['type'] == 'dfrobot_1':
|
||||||
|
from pwnagotchi.ui.hw.dfrobot import DFRobotV1
|
||||||
return DFRobotV1(config)
|
return DFRobotV1(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'dfrobot_2':
|
elif config['ui']['display']['type'] == 'dfrobot_2':
|
||||||
|
from pwnagotchi.ui.hw.dfrobot_v2 import DFRobotV2
|
||||||
return DFRobotV2(config)
|
return DFRobotV2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare144lcd':
|
elif config['ui']['display']['type'] == 'waveshare144lcd':
|
||||||
|
from pwnagotchi.ui.hw.waveshare1in44lcd import Waveshare144lcd
|
||||||
return Waveshare144lcd(config)
|
return Waveshare144lcd(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare35lcd':
|
elif config['ui']['display']['type'] == 'waveshare35lcd':
|
||||||
|
from pwnagotchi.ui.hw.waveshare3in5lcd import Waveshare35lcd
|
||||||
return Waveshare35lcd(config)
|
return Waveshare35lcd(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'spotpear24inch':
|
elif config['ui']['display']['type'] == 'spotpear24inch':
|
||||||
|
from pwnagotchi.ui.hw.spotpear24in import Spotpear24inch
|
||||||
return Spotpear24inch(config)
|
return Spotpear24inch(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'displayhatmini':
|
elif config['ui']['display']['type'] == 'displayhatmini':
|
||||||
|
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
|
||||||
return DisplayHatMini(config)
|
return DisplayHatMini(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'pirateaudio':
|
||||||
|
from pwnagotchi.ui.hw.pirateaudio import PirateAudio
|
||||||
|
return PirateAudio(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'pitft':
|
||||||
|
from pwnagotchi.ui.hw.pitft import Pitft
|
||||||
|
return Pitft(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'tftbonnet':
|
||||||
|
from pwnagotchi.ui.hw.tftbonnet import TftBonnet
|
||||||
|
return TftBonnet(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshareoledlcd':
|
||||||
|
from pwnagotchi.ui.hw.waveshareoledlcd import Waveshareoledlcd
|
||||||
|
return Waveshareoledlcd(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare1in02':
|
elif config['ui']['display']['type'] == 'waveshare1in02':
|
||||||
|
from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02
|
||||||
return Waveshare1in02(config)
|
return Waveshare1in02(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare1in54':
|
elif config['ui']['display']['type'] == 'waveshare1in54':
|
||||||
|
from pwnagotchi.ui.hw.waveshare1in54 import Waveshare154
|
||||||
return Waveshare154(config)
|
return Waveshare154(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare1in54_v2':
|
elif config['ui']['display']['type'] == 'waveshare1in54_v2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare1in54_V2 import Waveshare154V2
|
||||||
return Waveshare154V2(config)
|
return Waveshare154V2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare1in54b':
|
elif config['ui']['display']['type'] == 'waveshare1in54b':
|
||||||
|
from pwnagotchi.ui.hw.waveshare1in54b import Waveshare154inchb
|
||||||
return Waveshare154inchb(config)
|
return Waveshare154inchb(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare1in54b_v2':
|
elif config['ui']['display']['type'] == 'waveshare1in54b_v2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare1in54b_V2 import Waveshare154bV2
|
||||||
return Waveshare154bV2(config)
|
return Waveshare154bV2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare1in54c':
|
elif config['ui']['display']['type'] == 'waveshare1in54c':
|
||||||
|
from pwnagotchi.ui.hw.waveshare1in54c import Waveshare1in54c
|
||||||
return Waveshare1in54c(config)
|
return Waveshare1in54c(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare1in64g':
|
elif config['ui']['display']['type'] == 'waveshare1in64g':
|
||||||
|
from pwnagotchi.ui.hw.waveshare1in64g import Waveshare1in64g
|
||||||
return Waveshare1in64g(config)
|
return Waveshare1in64g(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in7':
|
elif config['ui']['display']['type'] == 'waveshare2in7':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in7 import Waveshare27inch
|
||||||
return Waveshare27inch(config)
|
return Waveshare27inch(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in7_v2':
|
elif config['ui']['display']['type'] == 'waveshare2in7_v2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in7_V2 import Waveshare27inchV2
|
||||||
return Waveshare27inchV2(config)
|
return Waveshare27inchV2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in7b':
|
elif config['ui']['display']['type'] == 'waveshare2in7b':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in7b import Waveshare27b
|
||||||
return Waveshare27b(config)
|
return Waveshare27b(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in7b_v2':
|
elif config['ui']['display']['type'] == 'waveshare2in7b_v2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in7b_V2 import Waveshare27bV2
|
||||||
return Waveshare27bV2(config)
|
return Waveshare27bV2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in9':
|
elif config['ui']['display']['type'] == 'waveshare2in9':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in9 import Waveshare29inch
|
||||||
return Waveshare29inch(config)
|
return Waveshare29inch(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in9bc':
|
elif config['ui']['display']['type'] == 'waveshare2in9bc':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in9bc import Waveshare2in9bc
|
||||||
return Waveshare2in9bc(config)
|
return Waveshare2in9bc(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in9d':
|
elif config['ui']['display']['type'] == 'waveshare2in9d':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in9d import Waveshare2in9d
|
||||||
return Waveshare2in9d(config)
|
return Waveshare2in9d(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in9_v2':
|
elif config['ui']['display']['type'] == 'waveshare2in9_v2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in9_V2 import Waveshare29inchV2
|
||||||
return Waveshare29inchV2(config)
|
return Waveshare29inchV2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in9b_v3':
|
elif config['ui']['display']['type'] == 'waveshare2in9b_v3':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in9b_V3 import Waveshare29bV3
|
||||||
return Waveshare29bV3(config)
|
return Waveshare29bV3(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in9b_v4':
|
elif config['ui']['display']['type'] == 'waveshare2in9b_v4':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in9b_V4 import Waveshare29bV4
|
||||||
return Waveshare29bV4(config)
|
return Waveshare29bV4(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare_1':
|
elif config['ui']['display']['type'] == 'waveshare_1':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in13 import WaveshareV1
|
||||||
return WaveshareV1(config)
|
return WaveshareV1(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare_2':
|
elif config['ui']['display']['type'] == 'waveshare_2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in13_V2 import WaveshareV2
|
||||||
return WaveshareV2(config)
|
return WaveshareV2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare_3':
|
elif config['ui']['display']['type'] == 'waveshare_3':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in13_V3 import WaveshareV3
|
||||||
return WaveshareV3(config)
|
return WaveshareV3(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare_4':
|
elif config['ui']['display']['type'] == 'waveshare_4':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in13_V4 import WaveshareV4
|
||||||
return WaveshareV4(config)
|
return WaveshareV4(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'adafruit2in13_v3':
|
||||||
|
from pwnagotchi.ui.hw.adafruit2in13 import Adafruit2in13V3
|
||||||
|
return Adafruit2in13V3(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in13bc':
|
elif config['ui']['display']['type'] == 'waveshare2in13bc':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in13bc import Waveshare213bc
|
||||||
return Waveshare213bc(config)
|
return Waveshare213bc(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in13d':
|
elif config['ui']['display']['type'] == 'waveshare2in13d':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in13d import Waveshare213d
|
||||||
return Waveshare213d(config)
|
return Waveshare213d(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in13b_v3':
|
elif config['ui']['display']['type'] == 'waveshare2in13b_v3':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in13b_V3 import Waveshare2in13bV3
|
||||||
return Waveshare2in13bV3(config)
|
return Waveshare2in13bV3(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in13b_v4':
|
elif config['ui']['display']['type'] == 'waveshare2in13b_v4':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in13b_V4 import Waveshare213bV4
|
||||||
return Waveshare213bV4(config)
|
return Waveshare213bV4(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in13g':
|
elif config['ui']['display']['type'] == 'waveshare2in13g':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in13g import Waveshare2in13g
|
||||||
return Waveshare2in13g(config)
|
return Waveshare2in13g(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in36g':
|
elif config['ui']['display']['type'] == 'waveshare2in36g':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in36g import Waveshare2in36g
|
||||||
return Waveshare2in36g(config)
|
return Waveshare2in36g(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in66':
|
elif config['ui']['display']['type'] == 'waveshare2in66':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in66 import Waveshare2in66
|
||||||
return Waveshare2in66(config)
|
return Waveshare2in66(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in66b':
|
elif config['ui']['display']['type'] == 'waveshare2in66b':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in66b import Waveshare2in66b
|
||||||
return Waveshare2in66b(config)
|
return Waveshare2in66b(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in66g':
|
elif config['ui']['display']['type'] == 'waveshare2in66g':
|
||||||
|
from pwnagotchi.ui.hw.waveshare2in66g import Waveshare2in66g
|
||||||
return Waveshare2in66g(config)
|
return Waveshare2in66g(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare3in0g':
|
elif config['ui']['display']['type'] == 'waveshare3in0g':
|
||||||
|
from pwnagotchi.ui.hw.waveshare3in0g import Waveshare3in0g
|
||||||
return Waveshare3in0g(config)
|
return Waveshare3in0g(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare3in7':
|
elif config['ui']['display']['type'] == 'waveshare3in7':
|
||||||
|
from pwnagotchi.ui.hw.waveshare3in7 import Waveshare3in7
|
||||||
return Waveshare3in7(config)
|
return Waveshare3in7(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare3in52':
|
elif config['ui']['display']['type'] == 'waveshare3in52':
|
||||||
|
from pwnagotchi.ui.hw.waveshare3in52 import Waveshare3in52
|
||||||
return Waveshare3in52(config)
|
return Waveshare3in52(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare4in01f':
|
elif config['ui']['display']['type'] == 'waveshare4in01f':
|
||||||
|
from pwnagotchi.ui.hw.waveshare4in01f import Waveshare4in01f
|
||||||
return Waveshare4in01f(config)
|
return Waveshare4in01f(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare4in2':
|
elif config['ui']['display']['type'] == 'waveshare4in2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare4in2 import Waveshare4in2
|
||||||
return Waveshare4in2(config)
|
return Waveshare4in2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare4in2_v2':
|
elif config['ui']['display']['type'] == 'waveshare4in2_v2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare4in2_V2 import Waveshare4in2V2
|
||||||
return Waveshare4in2V2(config)
|
return Waveshare4in2V2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare4in2b_v2':
|
elif config['ui']['display']['type'] == 'waveshare4in2b_v2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare4in2b_V2 import Waveshare4in2bV2
|
||||||
return Waveshare4in2bV2(config)
|
return Waveshare4in2bV2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare4in2bc':
|
elif config['ui']['display']['type'] == 'waveshare4in2bc':
|
||||||
|
from pwnagotchi.ui.hw.waveshare4in2bc import Waveshare4in2bc
|
||||||
return Waveshare4in2bc(config)
|
return Waveshare4in2bc(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare4in26':
|
elif config['ui']['display']['type'] == 'waveshare4in26':
|
||||||
|
from pwnagotchi.ui.hw.waveshare4in26 import Waveshare4in26
|
||||||
return Waveshare4in26(config)
|
return Waveshare4in26(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare4in37g':
|
elif config['ui']['display']['type'] == 'waveshare4in37g':
|
||||||
|
from pwnagotchi.ui.hw.waveshare4in37g import Waveshare4in37g
|
||||||
return Waveshare4in37g(config)
|
return Waveshare4in37g(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare5in65f':
|
elif config['ui']['display']['type'] == 'waveshare5in65f':
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in65f import Waveshare5in65f
|
||||||
return Waveshare5in65f(config)
|
return Waveshare5in65f(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshare5in79':
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in79 import Waveshare5in79
|
||||||
|
return Waveshare5in79(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshare5in79b':
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in79b import Waveshare5in79b
|
||||||
|
return Waveshare5in79b(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare5in83':
|
elif config['ui']['display']['type'] == 'waveshare5in83':
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in83 import Waveshare5in83
|
||||||
return Waveshare5in83(config)
|
return Waveshare5in83(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare5in83_v2':
|
elif config['ui']['display']['type'] == 'waveshare5in83_v2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in83_V2 import Waveshare5in83V2
|
||||||
return Waveshare5in83V2(config)
|
return Waveshare5in83V2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare5in83b_v2':
|
elif config['ui']['display']['type'] == 'waveshare5in83b_v2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in83b_V2 import Waveshare5in83bV2
|
||||||
return Waveshare5in83bV2(config)
|
return Waveshare5in83bV2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare5in83bc':
|
elif config['ui']['display']['type'] == 'waveshare5in83bc':
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in83bc import Waveshare5in83bc
|
||||||
return Waveshare5in83bc(config)
|
return Waveshare5in83bc(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare7in3f':
|
elif config['ui']['display']['type'] == 'waveshare7in3f':
|
||||||
|
from pwnagotchi.ui.hw.waveshare7in3f import Waveshare7in3f
|
||||||
return Waveshare7in3f(config)
|
return Waveshare7in3f(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare7in3g':
|
elif config['ui']['display']['type'] == 'waveshare7in3g':
|
||||||
|
from pwnagotchi.ui.hw.waveshare7in3g import Waveshare7in3g
|
||||||
return Waveshare7in3g(config)
|
return Waveshare7in3g(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare7in5':
|
elif config['ui']['display']['type'] == 'waveshare7in5':
|
||||||
|
from pwnagotchi.ui.hw.waveshare7in5 import Waveshare7in5
|
||||||
return Waveshare7in5(config)
|
return Waveshare7in5(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare7in5_HD':
|
elif config['ui']['display']['type'] == 'waveshare7in5_HD':
|
||||||
|
from pwnagotchi.ui.hw.waveshare7in5_HD import Waveshare7in5HD
|
||||||
return Waveshare7in5HD(config)
|
return Waveshare7in5HD(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare7in5_v2':
|
elif config['ui']['display']['type'] == 'waveshare7in5_v2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare7in5_V2 import Waveshare7in5V2
|
||||||
return Waveshare7in5V2(config)
|
return Waveshare7in5V2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare7in5b_HD':
|
elif config['ui']['display']['type'] == 'waveshare7in5b_HD':
|
||||||
|
from pwnagotchi.ui.hw.waveshare7in5b_HD import Waveshare7in5bHD
|
||||||
return Waveshare7in5bHD(config)
|
return Waveshare7in5bHD(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare7in5b_v2':
|
elif config['ui']['display']['type'] == 'waveshare7in5b_v2':
|
||||||
|
from pwnagotchi.ui.hw.waveshare7in5b_V2 import Waveshare7in5bV2
|
||||||
return Waveshare7in5bV2(config)
|
return Waveshare7in5bV2(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare7in5bc':
|
elif config['ui']['display']['type'] == 'waveshare7in5bc':
|
||||||
|
from pwnagotchi.ui.hw.waveshare7in5bc import Waveshare7in5bc
|
||||||
return Waveshare7in5bc(config)
|
return Waveshare7in5bc(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare13in3k':
|
elif config['ui']['display']['type'] == 'waveshare13in3k':
|
||||||
|
from pwnagotchi.ui.hw.waveshare13in3k import Waveshare13in3k
|
||||||
return Waveshare13in3k(config)
|
return Waveshare13in3k(config)
|
||||||
|
45
pwnagotchi/ui/hw/adafruit2in13.py
Normal file
45
pwnagotchi/ui/hw/adafruit2in13.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class Adafruit2in13V3(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Adafruit2in13V3, self).__init__(config, 'adafruit2in13_v3')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
|
self._layout['width'] = 250
|
||||||
|
self._layout['height'] = 122
|
||||||
|
self._layout['face'] = (0, 40)
|
||||||
|
self._layout['name'] = (5, 20)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (28, 0)
|
||||||
|
self._layout['uptime'] = (185, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
|
self._layout['friend_face'] = (0, 92)
|
||||||
|
self._layout['friend_name'] = (40, 94)
|
||||||
|
self._layout['shakes'] = (0, 109)
|
||||||
|
self._layout['mode'] = (225, 109)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (125, 20),
|
||||||
|
'font': fonts.status_font(fonts.Medium),
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing adafruit 2in13 V3 display")
|
||||||
|
from pwnagotchi.ui.hw.libs.adafruit.v2in13_v3.epd2in13_v3 import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init()
|
||||||
|
self._display.Clear(0xFF)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
buf = self._display.getbuffer(canvas)
|
||||||
|
self._display.displayPartial(buf)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.Clear(0xFF)
|
@ -34,7 +34,7 @@ class DisplayHatMini(DisplayImpl):
|
|||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("initializing Display Hat Mini")
|
logging.info("initializing Display Hat Mini")
|
||||||
from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789
|
from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789
|
||||||
self._display = ST7789(0,1,9,13)
|
self._display = ST7789(0, 1, 9, 13)
|
||||||
|
|
||||||
def render(self, canvas):
|
def render(self, canvas):
|
||||||
self._display.display(canvas)
|
self._display.display(canvas)
|
||||||
|
43
pwnagotchi/ui/hw/dummydisplay.py
Normal file
43
pwnagotchi/ui/hw/dummydisplay.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class DummyDisplay(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(DummyDisplay, self).__init__(config, 'DummyDisplay')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
width = 480 if 'width' not in self.config else self.config['width']
|
||||||
|
height = 720 if 'height' not in self.config else self.config['height']
|
||||||
|
fonts.setup(int(height/30), int(height/40), int(height/30), int(height/6), int(height/30), int(height/35))
|
||||||
|
self._layout['width'] = width
|
||||||
|
self._layout['height'] = height
|
||||||
|
self._layout['face'] = (0, int(width/12))
|
||||||
|
self._layout['name'] = (5, int(width/25))
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (int(width/8), 0)
|
||||||
|
self._layout['uptime'] = (width-int(width/12), 0)
|
||||||
|
self._layout['line1'] = [0, int(height/32), width, int(height/32)]
|
||||||
|
self._layout['line2'] = [0, height-int(height/25)-1, width, height-int(height/25)-1]
|
||||||
|
self._layout['friend_face'] = (0, int(height/10))
|
||||||
|
self._layout['friend_name'] = (int(width/12), int(height/10))
|
||||||
|
self._layout['shakes'] = (0, height-int(height/25))
|
||||||
|
self._layout['mode'] = (width-int(width/8), height - int (height/25))
|
||||||
|
lw, lh = fonts.Small.getsize("W")
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (int(width/48), int(height/3)),
|
||||||
|
'font': fonts.status_font(fonts.Small),
|
||||||
|
'max': int(width / lw)
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
return
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
return
|
@ -33,7 +33,7 @@ class LcdHat(DisplayImpl):
|
|||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("initializing lcdhat display")
|
logging.info("initializing lcdhat display")
|
||||||
from pwnagotchi.ui.hw.libs.waveshare.lcdhat.epd import EPD
|
from pwnagotchi.ui.hw.libs.waveshare.lcd.lcdhat.epd import EPD
|
||||||
self._display = EPD()
|
self._display = EPD()
|
||||||
self._display.init()
|
self._display.init()
|
||||||
self._display.clear()
|
self._display.clear()
|
||||||
|
0
pwnagotchi/ui/hw/libs/__init__.py
Normal file
0
pwnagotchi/ui/hw/libs/__init__.py
Normal file
132
pwnagotchi/ui/hw/libs/adafruit/epdconfig.py
Normal file
132
pwnagotchi/ui/hw/libs/adafruit/epdconfig.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
# /*****************************************************************************
|
||||||
|
# * | File : epdconfig.py
|
||||||
|
# * | Author : Waveshare team
|
||||||
|
# * | Function : Hardware underlying interface
|
||||||
|
# * | Info :
|
||||||
|
# *----------------
|
||||||
|
# * | This version: V1.2
|
||||||
|
# * | Date : 2022-10-29
|
||||||
|
# * | Info :
|
||||||
|
# ******************************************************************************
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documnetation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RaspberryPi:
|
||||||
|
# Pin definition
|
||||||
|
RST_PIN = 27
|
||||||
|
DC_PIN = 22
|
||||||
|
CS_PIN = 8
|
||||||
|
BUSY_PIN = 17
|
||||||
|
PWR_PIN = 18
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import spidev
|
||||||
|
import gpiozero
|
||||||
|
|
||||||
|
self.SPI = spidev.SpiDev()
|
||||||
|
self.GPIO_RST_PIN = gpiozero.LED(self.RST_PIN)
|
||||||
|
self.GPIO_DC_PIN = gpiozero.LED(self.DC_PIN)
|
||||||
|
self.GPIO_CS_PIN = gpiozero.LED(self.CS_PIN)
|
||||||
|
self.GPIO_PWR_PIN = gpiozero.LED(self.PWR_PIN)
|
||||||
|
self.GPIO_BUSY_PIN = gpiozero.Button(self.BUSY_PIN, pull_up=False)
|
||||||
|
|
||||||
|
def digital_write(self, pin, value):
|
||||||
|
if pin == self.RST_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_RST_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_RST_PIN.off()
|
||||||
|
elif pin == self.DC_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_DC_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_DC_PIN.off()
|
||||||
|
elif pin == self.CS_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_CS_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_CS_PIN.off()
|
||||||
|
elif pin == self.PWR_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_PWR_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_PWR_PIN.off()
|
||||||
|
|
||||||
|
def digital_read(self, pin):
|
||||||
|
if pin == self.BUSY_PIN:
|
||||||
|
return self.GPIO_BUSY_PIN.value
|
||||||
|
elif pin == self.RST_PIN:
|
||||||
|
return self.RST_PIN.value
|
||||||
|
elif pin == self.DC_PIN:
|
||||||
|
return self.DC_PIN.value
|
||||||
|
elif pin == self.CS_PIN:
|
||||||
|
return self.CS_PIN.value
|
||||||
|
elif pin == self.PWR_PIN:
|
||||||
|
return self.PWR_PIN.value
|
||||||
|
|
||||||
|
def delay_ms(self, delaytime):
|
||||||
|
time.sleep(delaytime / 1000.0)
|
||||||
|
|
||||||
|
def spi_writebyte(self, data):
|
||||||
|
self.SPI.writebytes(data)
|
||||||
|
|
||||||
|
def spi_writebyte2(self, data):
|
||||||
|
self.SPI.writebytes2(data)
|
||||||
|
|
||||||
|
def module_init(self):
|
||||||
|
self.GPIO_PWR_PIN.on()
|
||||||
|
|
||||||
|
# SPI device, bus = 0, device = 0
|
||||||
|
self.SPI.open(0, 0)
|
||||||
|
self.SPI.max_speed_hz = 4000000
|
||||||
|
self.SPI.mode = 0b00
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def module_exit(self, cleanup=False):
|
||||||
|
logger.debug("spi end")
|
||||||
|
self.SPI.close()
|
||||||
|
|
||||||
|
self.GPIO_RST_PIN.off()
|
||||||
|
self.GPIO_DC_PIN.off()
|
||||||
|
self.GPIO_PWR_PIN.off()
|
||||||
|
logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||||
|
|
||||||
|
if cleanup:
|
||||||
|
self.GPIO_RST_PIN.close()
|
||||||
|
self.GPIO_DC_PIN.close()
|
||||||
|
self.GPIO_CS_PIN.close()
|
||||||
|
self.GPIO_PWR_PIN.close()
|
||||||
|
self.GPIO_BUSY_PIN.close()
|
||||||
|
|
||||||
|
|
||||||
|
implementation = RaspberryPi()
|
||||||
|
|
||||||
|
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||||
|
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||||
|
|
||||||
|
### END OF FILE ###
|
362
pwnagotchi/ui/hw/libs/adafruit/pitft/ILI9341.py
Normal file
362
pwnagotchi/ui/hw/libs/adafruit/pitft/ILI9341.py
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
# Modified for Pwnagotchi by RasTacsko
|
||||||
|
# Based on ST7899 driver for pimoroni displayhatmini by Do-Ki
|
||||||
|
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
__version__ = '0.0.1'
|
||||||
|
|
||||||
|
# Constants for interacting with display registers.
|
||||||
|
ILI9341_TFTWIDTH = 320
|
||||||
|
ILI9341_TFTHEIGHT = 240
|
||||||
|
|
||||||
|
ILI9341_NOP = 0x00
|
||||||
|
ILI9341_SWRESET = 0x01
|
||||||
|
ILI9341_RDDID = 0x04
|
||||||
|
ILI9341_RDDST = 0x09
|
||||||
|
|
||||||
|
ILI9341_SLPIN = 0x10
|
||||||
|
ILI9341_SLPOUT = 0x11
|
||||||
|
ILI9341_PTLON = 0x12
|
||||||
|
ILI9341_NORON = 0x13
|
||||||
|
|
||||||
|
ILI9341_RDMODE = 0x0A
|
||||||
|
ILI9341_RDMADCTL = 0x0B
|
||||||
|
ILI9341_RDPIXFMT = 0x0C
|
||||||
|
ILI9341_RDIMGFMT = 0x0A
|
||||||
|
ILI9341_RDSELFDIAG = 0x0F
|
||||||
|
|
||||||
|
ILI9341_INVOFF = 0x20
|
||||||
|
ILI9341_INVON = 0x21
|
||||||
|
ILI9341_GAMMASET = 0x26
|
||||||
|
ILI9341_DISPOFF = 0x28
|
||||||
|
ILI9341_DISPON = 0x29
|
||||||
|
|
||||||
|
ILI9341_CASET = 0x2A
|
||||||
|
ILI9341_PASET = 0x2B
|
||||||
|
ILI9341_RAMWR = 0x2C
|
||||||
|
ILI9341_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ILI9341_PTLAR = 0x30
|
||||||
|
ILI9341_MADCTL = 0x36
|
||||||
|
ILI9341_PIXFMT = 0x3A
|
||||||
|
|
||||||
|
ILI9341_FRMCTR1 = 0xB1
|
||||||
|
ILI9341_FRMCTR2 = 0xB2
|
||||||
|
ILI9341_FRMCTR3 = 0xB3
|
||||||
|
ILI9341_INVCTR = 0xB4
|
||||||
|
ILI9341_DFUNCTR = 0xB6
|
||||||
|
|
||||||
|
ILI9341_PWCTR1 = 0xC0
|
||||||
|
ILI9341_PWCTR2 = 0xC1
|
||||||
|
ILI9341_PWCTR3 = 0xC2
|
||||||
|
ILI9341_PWCTR4 = 0xC3
|
||||||
|
ILI9341_PWCTR5 = 0xC4
|
||||||
|
ILI9341_VMCTR1 = 0xC5
|
||||||
|
ILI9341_VMCTR2 = 0xC7
|
||||||
|
|
||||||
|
ILI9341_RDID1 = 0xDA
|
||||||
|
ILI9341_RDID2 = 0xDB
|
||||||
|
ILI9341_RDID3 = 0xDC
|
||||||
|
ILI9341_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ILI9341_GMCTRP1 = 0xE0
|
||||||
|
ILI9341_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ILI9341_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
|
||||||
|
class ILI9341(object):
|
||||||
|
"""Representation of an ILI9341 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None,
|
||||||
|
width=ILI9341_TFTWIDTH, height=ILI9341_TFTHEIGHT,
|
||||||
|
rotation=270, invert=False, spi_speed_hz=64000000,
|
||||||
|
offset_left=0, offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
:param port: SPI port number -> 0
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM) -> 1
|
||||||
|
:param backlight: Pin for controlling backlight -> 18
|
||||||
|
:param rst: Reset pin for ILI9341 -> 24?
|
||||||
|
:param width: Width of display connected to ILI9341 -> 240
|
||||||
|
:param height: Height of display connected to ILI9341 -> 320
|
||||||
|
:param rotation: Rotation of display connected to ILI9341
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.05)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
# Create an image buffer.
|
||||||
|
self.buffer = Image.new('RGB', (width, height))
|
||||||
|
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start+chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.005)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.02)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.150)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display. Broken out as a separate function so it can
|
||||||
|
# be overridden by other displays in the future.
|
||||||
|
self.command(0xEF)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x80)
|
||||||
|
self.data(0x02)
|
||||||
|
self.command(0xCF)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0XC1)
|
||||||
|
self.data(0X30)
|
||||||
|
self.command(0xED)
|
||||||
|
self.data(0x64)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0X12)
|
||||||
|
self.data(0X81)
|
||||||
|
self.command(0xE8)
|
||||||
|
self.data(0x85)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x78)
|
||||||
|
self.command(0xCB)
|
||||||
|
self.data(0x39)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x34)
|
||||||
|
self.data(0x02)
|
||||||
|
self.command(0xF7)
|
||||||
|
self.data(0x20)
|
||||||
|
self.command(0xEA)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_PWCTR1) # Power control
|
||||||
|
self.data(0x23) # VRH[5:0]
|
||||||
|
self.command(ILI9341_PWCTR2) # Power control
|
||||||
|
self.data(0x10) # SAP[2:0];BT[3:0]
|
||||||
|
self.command(ILI9341_VMCTR1) # VCM control
|
||||||
|
self.data(0x3e)
|
||||||
|
self.data(0x28)
|
||||||
|
self.command(ILI9341_VMCTR2) # VCM control2
|
||||||
|
self.data(0x86) # --
|
||||||
|
self.command(ILI9341_MADCTL) # Memory Access Control
|
||||||
|
self.data(0x48)
|
||||||
|
self.command(ILI9341_PIXFMT)
|
||||||
|
self.data(0x55)
|
||||||
|
self.command(ILI9341_FRMCTR1)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x18)
|
||||||
|
self.command(ILI9341_DFUNCTR) # Display Function Control
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x82)
|
||||||
|
self.data(0x27)
|
||||||
|
self.command(0xF2) # 3Gamma Function Disable
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_GAMMASET) # Gamma curve selected
|
||||||
|
self.data(0x01)
|
||||||
|
self.command(ILI9341_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0x0F)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x4E)
|
||||||
|
self.data(0xF1)
|
||||||
|
self.data(0x37)
|
||||||
|
self.data(0x07)
|
||||||
|
self.data(0x10)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x09)
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x14)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x07)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0xC1)
|
||||||
|
self.data(0x48)
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x0F)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0x36)
|
||||||
|
self.data(0x0F)
|
||||||
|
if self._invert:
|
||||||
|
self.command(ILI9341_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ILI9341_INVOFF) # Don't invert display
|
||||||
|
self.command(ILI9341_SLPOUT) # Exit Sleep
|
||||||
|
time.sleep(0.120)
|
||||||
|
self.command(ILI9341_DISPON) # Display on
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display deprecated.
|
||||||
|
Included in __init__. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to 239,319.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self.width-1
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self.height-1
|
||||||
|
|
||||||
|
self.command(ILI9341_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ILI9341_PASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ILI9341_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
360
pwnagotchi/ui/hw/libs/adafruit/tftbonnet/ST7789.py
Normal file
360
pwnagotchi/ui/hw/libs/adafruit/tftbonnet/ST7789.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = '0.0.4'
|
||||||
|
|
||||||
|
BG_SPI_CS_BACK = 0
|
||||||
|
BG_SPI_CS_FRONT = 1
|
||||||
|
|
||||||
|
SPI_CLOCK_HZ = 16000000
|
||||||
|
|
||||||
|
ST7789_NOP = 0x00
|
||||||
|
ST7789_SWRESET = 0x01
|
||||||
|
ST7789_RDDID = 0x04
|
||||||
|
ST7789_RDDST = 0x09
|
||||||
|
|
||||||
|
ST7789_SLPIN = 0x10
|
||||||
|
ST7789_SLPOUT = 0x11
|
||||||
|
ST7789_PTLON = 0x12
|
||||||
|
ST7789_NORON = 0x13
|
||||||
|
|
||||||
|
ST7789_INVOFF = 0x20
|
||||||
|
ST7789_INVON = 0x21
|
||||||
|
ST7789_DISPOFF = 0x28
|
||||||
|
ST7789_DISPON = 0x29
|
||||||
|
|
||||||
|
ST7789_CASET = 0x2A
|
||||||
|
ST7789_RASET = 0x2B
|
||||||
|
ST7789_RAMWR = 0x2C
|
||||||
|
ST7789_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ST7789_PTLAR = 0x30
|
||||||
|
ST7789_MADCTL = 0x36
|
||||||
|
ST7789_COLMOD = 0x3A
|
||||||
|
|
||||||
|
ST7789_FRMCTR1 = 0xB1
|
||||||
|
ST7789_FRMCTR2 = 0xB2
|
||||||
|
ST7789_FRMCTR3 = 0xB3
|
||||||
|
ST7789_INVCTR = 0xB4
|
||||||
|
ST7789_DISSET5 = 0xB6
|
||||||
|
|
||||||
|
ST7789_GCTRL = 0xB7
|
||||||
|
ST7789_GTADJ = 0xB8
|
||||||
|
ST7789_VCOMS = 0xBB
|
||||||
|
|
||||||
|
ST7789_LCMCTRL = 0xC0
|
||||||
|
ST7789_IDSET = 0xC1
|
||||||
|
ST7789_VDVVRHEN = 0xC2
|
||||||
|
ST7789_VRHS = 0xC3
|
||||||
|
ST7789_VDVS = 0xC4
|
||||||
|
ST7789_VMCTR1 = 0xC5
|
||||||
|
ST7789_FRCTRL2 = 0xC6
|
||||||
|
ST7789_CABCCTRL = 0xC7
|
||||||
|
|
||||||
|
ST7789_RDID1 = 0xDA
|
||||||
|
ST7789_RDID2 = 0xDB
|
||||||
|
ST7789_RDID3 = 0xDC
|
||||||
|
ST7789_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ST7789_GMCTRP1 = 0xE0
|
||||||
|
ST7789_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ST7789_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
|
||||||
|
class ST7789(object):
|
||||||
|
"""Representation of an ST7789 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None, width=240,
|
||||||
|
height=240, rotation=90, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||||
|
offset_left=0,
|
||||||
|
offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
|
||||||
|
:param port: SPI port number
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM
|
||||||
|
:param backlight: Pin for controlling backlight
|
||||||
|
:param rst: Reset pin for ST7789
|
||||||
|
:param width: Width of display connected to ST7789
|
||||||
|
:param height: Height of display connected to ST7789
|
||||||
|
:param rotation: Rotation of display connected to ST7789
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
if width != height and rotation in [90, 270]:
|
||||||
|
raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.1)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start + chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display.
|
||||||
|
|
||||||
|
self.command(ST7789_SWRESET) # Software reset
|
||||||
|
time.sleep(0.150) # delay 150 ms
|
||||||
|
|
||||||
|
self.command(ST7789_MADCTL)
|
||||||
|
self.data(0x70)
|
||||||
|
|
||||||
|
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x33)
|
||||||
|
self.data(0x33)
|
||||||
|
|
||||||
|
self.command(ST7789_COLMOD)
|
||||||
|
self.data(0x05)
|
||||||
|
|
||||||
|
self.command(ST7789_GCTRL)
|
||||||
|
self.data(0x14)
|
||||||
|
|
||||||
|
self.command(ST7789_VCOMS)
|
||||||
|
self.data(0x37)
|
||||||
|
|
||||||
|
self.command(ST7789_LCMCTRL) # Power control
|
||||||
|
self.data(0x2C)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVVRHEN) # Power control
|
||||||
|
self.data(0x01)
|
||||||
|
|
||||||
|
self.command(ST7789_VRHS) # Power control
|
||||||
|
self.data(0x12)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVS) # Power control
|
||||||
|
self.data(0x20)
|
||||||
|
|
||||||
|
self.command(0xD0)
|
||||||
|
self.data(0xA4)
|
||||||
|
self.data(0xA1)
|
||||||
|
|
||||||
|
self.command(ST7789_FRCTRL2)
|
||||||
|
self.data(0x0F)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x54)
|
||||||
|
self.data(0x4C)
|
||||||
|
self.data(0x18)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x0B)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x44)
|
||||||
|
self.data(0x51)
|
||||||
|
self.data(0x2F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x20)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
if self._invert:
|
||||||
|
self.command(ST7789_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ST7789_INVOFF) # Don't invert display
|
||||||
|
|
||||||
|
self.command(ST7789_SLPOUT)
|
||||||
|
|
||||||
|
self.command(ST7789_DISPON) # Display on
|
||||||
|
time.sleep(0.100) # 100 ms
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display
|
||||||
|
|
||||||
|
Deprecated. Included in __init__.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to width-1,height-1.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self._width - 1
|
||||||
|
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self._height - 1
|
||||||
|
|
||||||
|
y0 += self._offset_top
|
||||||
|
y1 += self._offset_top
|
||||||
|
|
||||||
|
x0 += self._offset_left
|
||||||
|
x1 += self._offset_left
|
||||||
|
|
||||||
|
self.command(ST7789_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ST7789_RASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ST7789_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user