mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Compare commits
497 Commits
Author | SHA1 | Date | |
---|---|---|---|
fb7ef7b1dd | |||
28ff1429ac | |||
68941abc08 | |||
a7379d18e6 | |||
f9318ef27a | |||
cedb3d35ff | |||
4861a0c08b | |||
766463280d | |||
29e278da57 | |||
7e6d198c9b | |||
4029b2ffa0 | |||
32d244ec6f | |||
79e141320c | |||
330110843c | |||
dc5b297a82 | |||
e8f23d654c | |||
aedbf6bbfb | |||
0b3fc98f1c | |||
7edaa9040e | |||
6b6f5923ee | |||
d6bd65333e | |||
f81f1cedfc | |||
f6fa4ef39e | |||
e2ac10babb | |||
bff310ea3e | |||
a53130321f | |||
730cdad7cd | |||
85746046e1 | |||
fbe8211a10 | |||
ea4f495ab8 | |||
afb840212d | |||
82b64baaed | |||
244d3c03f9 | |||
c440a73824 | |||
48b3d6dcb5 | |||
b9531c60a2 | |||
27aac52e1f | |||
77998cd214 | |||
03c33e1be3 | |||
4f63af63a8 | |||
6ceb27f837 | |||
5d5cde5409 | |||
2630068c7b | |||
de940d0e77 | |||
0f36a10567 | |||
65a93812ef | |||
022ebccf30 | |||
e5812e86c1 | |||
1ee940c798 | |||
e3f0da6193 | |||
f9efbb56cd | |||
89a589af72 | |||
a100933678 | |||
3584f3f551 | |||
3fdcebc90c | |||
e33bec1d31 | |||
de0a09e39d | |||
dbb83b4825 | |||
956c083985 | |||
62c324730c | |||
8dc6f78610 | |||
0542c002d0 | |||
702d463194 | |||
087fd9cf09 | |||
28335419f6 | |||
db444588dd | |||
de08804b7f | |||
9d6a7e9441 | |||
14480346fe | |||
7b6ad4e99a | |||
313edb1524 | |||
820f2cef59 | |||
c76d9f4909 | |||
69ff6cd03c | |||
09f6281666 | |||
2f1c216387 | |||
87d8beb9be | |||
e63be1ffd8 | |||
d5755103a0 | |||
2014c22262 | |||
5295692da9 | |||
b7eb86d55c | |||
81c3581138 | |||
7d8b66e1cb | |||
689d39c450 | |||
8eccd71c6b | |||
caf73700fd | |||
e9c12dde94 | |||
3f40c0b7b3 | |||
318e3480ce | |||
1614d1f94d | |||
4e1f08f6a8 | |||
cbc601e38b | |||
20bae7adaa | |||
ed62ac0b84 | |||
f0ecee0555 | |||
8e99ae419d | |||
88957059ef | |||
c42e8d0f5d | |||
5b5718c186 | |||
70f0dcb891 | |||
8f02c36d63 | |||
9941f093e9 | |||
de07226be1 | |||
6603e01005 | |||
e2fa3ca138 | |||
357ffc67e5 | |||
ea8fb5e533 | |||
31afd6d0ba | |||
b38274e652 | |||
518e8c219c | |||
4e07fbf1aa | |||
b3e81a95c8 | |||
050966215b | |||
fef442edbb | |||
1064936503 | |||
a762a7f763 | |||
8fd0e358a9 | |||
9a941c1d46 | |||
ead5a3baac | |||
ac2973889d | |||
8991dd6811 | |||
415e5cd551 | |||
47705ba1a2 | |||
7c7bbc770b | |||
ab83de4905 | |||
8f7741cd9e | |||
835886e6f6 | |||
6b2039a8e6 | |||
d53d0c7841 | |||
6c40998b51 | |||
a71a90ba3d | |||
b03f6f747b | |||
a8ba88c9cc | |||
f597bd6d29 | |||
a3f103ac06 | |||
e0a068e51d | |||
dc1b3c7635 | |||
3657859a73 | |||
33ff5a0bf8 | |||
666f65c640 | |||
84d45a0d45 | |||
06a4491008 | |||
5485f83ca6 | |||
8a242a707b | |||
8a3eacb5d2 | |||
ab541458fa | |||
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 | |||
611a3e7fb5 | |||
599c6211e4 | |||
eb48d29851 | |||
0999b95be0 | |||
ae351e5e9c | |||
fa58136c0e | |||
3d5185f2c1 | |||
b6bb7b9080 | |||
20715351af | |||
1d3c781d12 | |||
bd51b4330f | |||
f216a21132 | |||
5ae357c3dc | |||
ed9afe6856 | |||
25d932fa9b | |||
ebd25d50b0 | |||
0585fe75fe | |||
27e784a5d7 | |||
c02efb5224 | |||
a7634a2b4a | |||
ca4feb895e | |||
107d366c82 | |||
488968ba7f | |||
0dd8f72c95 | |||
0e274af5a0 | |||
8627c8800d | |||
53ed6f61c6 | |||
835523241b | |||
418dbf21e3 | |||
9a40567daa | |||
eab331686d | |||
e1d8001fb4 | |||
3cc172a526 | |||
41afe302d9 | |||
5bcca7e89b | |||
4a12436942 | |||
d8b6363d37 | |||
b3d8a44208 | |||
3fb91498cd | |||
7587f38c28 | |||
d7fefa78f0 | |||
1ef3a88679 | |||
291ef39563 | |||
16d80a09e6 | |||
4ffad03c91 | |||
0638832532 | |||
f895b06978 | |||
f410feb2bb | |||
1ffe6c4e3d | |||
dfbfd7f891 | |||
a50695aef2 | |||
829a6962d6 | |||
ce1315b85f | |||
6a2703cc27 | |||
502e856934 | |||
dffcbbf447 | |||
333804c3bb | |||
50b1ceed81 | |||
877b9fbba3 | |||
8724ca546d | |||
961d3a536a | |||
c882d0b67a | |||
12fd081ae0 | |||
e1be0f7674 | |||
396b30f780 | |||
e7d8d632a0 | |||
5a6ec7b4b8 | |||
92cd5d3fdb | |||
99caa7a973 | |||
a94e7eef02 | |||
1cefae55d1 | |||
c5ee1df855 | |||
d142840307 | |||
e558146e28 | |||
11a3330153 | |||
53b2dd8628 | |||
6381f9443b | |||
fa7e87b974 | |||
cca2ff2da4 | |||
78415b3137 | |||
34284aa1bc | |||
56ebb60662 | |||
84f6624844 | |||
3bcbb0ce9a | |||
e01e457992 | |||
1780859889 | |||
fdd98bb37a | |||
86991304a5 | |||
4f62759d6d | |||
7b7ba02aad | |||
7040be2d30 | |||
d0617ccfaf | |||
a0b5078b64 | |||
54c1ffd63c | |||
7530709d0c | |||
a9a6fd424b | |||
8c97301992 | |||
91eaa22188 | |||
31a4af4c21 | |||
dceeaff1fb | |||
c0241dc8df | |||
8f405f4ab2 | |||
bd79e71563 | |||
7f662585aa | |||
d23ff8d47a | |||
04435229fe | |||
42e236bafe | |||
5081b72695 | |||
652740f050 | |||
1b6b12bdf5 | |||
c610335a34 | |||
6c39ed97dd | |||
967f1663c6 | |||
ffe1e90ccd | |||
f2d2bcbfdf | |||
bfc0795fb8 | |||
1a55afd74a | |||
1594e7c129 | |||
a244e70a1c | |||
d35d5d6c3c | |||
7aacf9fb44 | |||
dde6fa4c2a | |||
7dfb2f2ff6 | |||
cf46ab3da9 | |||
6824e541a4 | |||
1c4e8fa750 | |||
a50ec4a65c | |||
5414fdf21d | |||
c9883bb4f9 | |||
045f5853bc | |||
6dfcaf05d5 | |||
e2f8119d2c | |||
d675b3d0dd | |||
30850b6530 | |||
1e9c9bae42 | |||
64241515ad | |||
c9f232dae2 | |||
4f97bcaa83 | |||
7d2a03c79f | |||
f89ba85f73 | |||
57f03f4359 | |||
9bc266f9ff | |||
b0db0285bc | |||
cca3e77d50 | |||
4fe603bf5e | |||
9a9ee70a78 | |||
7fc8838f76 | |||
94b2ff7047 | |||
8edb0fdaa4 | |||
b557768159 | |||
5a6967eb4d | |||
c0c35231cf | |||
88362b3354 | |||
e50cf04967 | |||
85a64a3914 | |||
63d0c20a3e | |||
044639a6d4 | |||
7fc2c6b25f | |||
c5d5d9bb14 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1,4 +1,3 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
patreon: pwnagotchi_torch
|
|
||||||
github: jayofelony
|
github: jayofelony
|
55
.github/workflows/publish.yml
vendored
Normal file
55
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
name: Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: ${{ matrix.name }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: "Raspberry Pi 32-bit"
|
||||||
|
id: "32bit"
|
||||||
|
- name: "Raspberry Pi 64-bit"
|
||||||
|
id: "64bit"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: publish/build
|
||||||
|
|
||||||
|
- name: Extract version from file
|
||||||
|
id: get_version
|
||||||
|
run: |
|
||||||
|
VERSION=$(cut -d "'" -f2 < publish/build/pwnagotchi/_version.py)
|
||||||
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install qemu dependencies
|
||||||
|
run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y
|
||||||
|
|
||||||
|
- name: Build ${{ matrix.name }} img file
|
||||||
|
run: cd publish/build; ls -la .; pwd; make packer; make ${{ matrix.id }}
|
||||||
|
|
||||||
|
- name: Change name of .img.xz to add version
|
||||||
|
run: |
|
||||||
|
sudo chown runner:docker "pwnagotchi-${{ matrix.id }}.img"
|
||||||
|
mv "pwnagotchi-${{ matrix.id }}.img" "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img"
|
||||||
|
|
||||||
|
- name: PiShrink
|
||||||
|
run: |
|
||||||
|
wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
|
||||||
|
chmod +x pishrink.sh
|
||||||
|
sudo mv pishrink.sh /usr/local/bin
|
||||||
|
sudo pishrink.sh -aZ "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img"
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
prerelease: false
|
||||||
|
make_latest: true
|
||||||
|
tag_name: v${{ env.VERSION }}
|
||||||
|
name: Pwnagotchi v${{ env.VERSION }}
|
||||||
|
files: pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img.xz
|
||||||
|
generate_release_notes: true
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
*.pyc
|
4
.idea/deployment.xml
generated
4
.idea/deployment.xml
generated
@ -1,4 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="PublishConfigData" filePermissions="493" folderPermissions="493" remoteFilesAllowedToDisappearOnAutoupload="false" />
|
<component name="PublishConfigData" filePermissions="493" folderPermissions="493" remoteFilesAllowedToDisappearOnAutoupload="false" confirmBeforeUploading="false">
|
||||||
|
<option name="confirmBeforeUploading" value="false" />
|
||||||
|
</component>
|
||||||
</project>
|
</project>
|
5
.idea/misc.xml
generated
5
.idea/misc.xml
generated
@ -3,5 +3,8 @@
|
|||||||
<component name="Black">
|
<component name="Black">
|
||||||
<option name="sdkName" value="Python 3.11 (pwnagotchi)" />
|
<option name="sdkName" value="Python 3.11 (pwnagotchi)" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (pwnagotchi-torch-bookworm)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (pwnagotchi)" project-jdk-type="Python SDK" />
|
||||||
|
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
</project>
|
</project>
|
2
.idea/pwnagotchi.iml
generated
2
.idea/pwnagotchi.iml
generated
@ -4,7 +4,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.10 (pwnagotchi-torch-bookworm)" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.12 (pwnagotchi)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PyDocumentationSettings">
|
<component name="PyDocumentationSettings">
|
||||||
|
10
.idea/sshConfigs.xml
generated
Normal file
10
.idea/sshConfigs.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SshConfigs">
|
||||||
|
<configs>
|
||||||
|
<sshConfig authType="PASSWORD" connectionConfig="{"serverAliveInterval":300}" id="8b69df7d-cec5-421f-8edf-53ed6233f6b6" port="22" customName="pwnagotchi" nameFormat="CUSTOM" useOpenSSHConfig="true">
|
||||||
|
<option name="customName" value="pwnagotchi" />
|
||||||
|
</sshConfig>
|
||||||
|
</configs>
|
||||||
|
</component>
|
||||||
|
</project>
|
14
.idea/webServers.xml
generated
Normal file
14
.idea/webServers.xml
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="WebServers">
|
||||||
|
<option name="servers">
|
||||||
|
<webServer id="cf2a1148-a103-4472-a782-7debdc6dabf9" name="pwnagotchi">
|
||||||
|
<fileTransfer accessType="SFTP" port="22" sshConfigId="8b69df7d-cec5-421f-8edf-53ed6233f6b6" sshConfig="pwnagotchi">
|
||||||
|
<advancedOptions>
|
||||||
|
<advancedOptions dataProtectionLevel="Private" passiveMode="true" shareSSLContext="true" />
|
||||||
|
</advancedOptions>
|
||||||
|
</fileTransfer>
|
||||||
|
</webServer>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
38
Makefile
38
Makefile
@ -1,4 +1,4 @@
|
|||||||
PACKER_VERSION := 1.10.0
|
PACKER_VERSION := 1.11.0
|
||||||
PWN_HOSTNAME := pwnagotchi
|
PWN_HOSTNAME := pwnagotchi
|
||||||
PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py)
|
PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py)
|
||||||
|
|
||||||
@ -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: packer image
|
||||||
|
|
||||||
update_langs:
|
update_langs:
|
||||||
@for lang in pwnagotchi/locale/*/; do\
|
@for lang in pwnagotchi/locale/*/; do\
|
||||||
@ -39,26 +40,23 @@ compile_langs:
|
|||||||
./scripts/language.sh compile $$(basename $$lang); \
|
./scripts/language.sh compile $$(basename $$lang); \
|
||||||
done
|
done
|
||||||
|
|
||||||
PACKER := ~/pwnagotchi/packer
|
packer:
|
||||||
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 -o /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 $@
|
|
||||||
|
|
||||||
# Building the image requires packer, but don't rebuild the image just because packer updated.
|
image: packer
|
||||||
pwnagotchi: | $(PACKER)
|
export LC_ALL=en_GB.UTF-8
|
||||||
|
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
|
||||||
|
|
||||||
# If the packer or ansible files are updated, rebuild the image.
|
32bit: packer
|
||||||
pwnagotchi: 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 raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi32.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
|
64bit: packer
|
||||||
|
export LC_ALL=en_GB.UTF-8
|
||||||
.PHONY: image
|
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
|
||||||
image: pwnagotchi
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
- rm -rf build dist pwnagotchi.egg-info
|
- rm -rf /tmp/packer*
|
||||||
- rm -f $(PACKER)
|
- rm -rf /tmp/LICENSE.txt
|
||||||
|
42
README.md
42
README.md
@ -1,32 +1,17 @@
|
|||||||
# 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)
|
||||||
|
|
||||||
It seems the Pi 5 is unable to run in monitor mode, will keep you updated on this.
|
**For installation docs check out the [wiki](https://github.com/jayofelony/pwnagotchi/wiki)!**
|
||||||
|
|
||||||
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 latest image file [here](https://github.com/jayofelony/pwnagotchi-bookworm/releases/tag/v2.6.9), 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.**
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
**Proudly partnering with [PiSugar](https://www.pisugar.com)!!**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding Wi-Fi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
|
[Pwnagotchi](https://pwnagotchi.org/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding Wi-Fi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
|
||||||
full and half WPA handshakes.
|
full and half WPA handshakes.
|
||||||
|
|
||||||

|

|
||||||
@ -41,18 +26,17 @@ Multiple units within close physical proximity can "talk" to each other, adverti
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
https://www.pwnagotchi.ai
|
https://github.com/jayofelony/pwnagotchi/wiki
|
||||||
|
https://www.pwnagotchi.org
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
| | Official Links |
|
| | Official Links |
|
||||||
|-----------|-------------------------------------------------------------|
|
|-----------|-------------------------------------------------------------|
|
||||||
| Website | [pwnagotchi.ai](https://pwnagotchi.ai/) |
|
| Website | [pwnagotchi.org](https://pwnagotchi.org/) |
|
||||||
| Forum | [community.pwnagotchi.ai](https://community.pwnagotchi.ai/) |
|
| Forum | [discord.gg](https://discord.gg/PGgnzFbz4M) |
|
||||||
| Slack | [pwnagotchi.slack.com](https://invite.pwnagotchi.ai/) |
|
|
||||||
| Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/) |
|
| Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/) |
|
||||||
| Twitter | [@pwnagotchi](https://twitter.com/pwnagotchi) |
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It is released under the GPL3 license.
|
`pwnagotchi` created by [@evilsocket](https://twitter.com/evilsocket) and updated by [us](https://github.com/jayofelony/pwnagotchi/graphs/contributors). It is released under the GPL3 license.
|
||||||
|
133
bin/pwnagotchi
133
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
|
||||||
@ -59,6 +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(1)
|
||||||
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():
|
||||||
@ -87,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:
|
||||||
@ -132,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,
|
||||||
@ -156,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-L501\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://www.patreon.com/pwnagotchi_torch \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)
|
||||||
@ -216,7 +323,7 @@ def pwnagotchi_cli():
|
|||||||
|
|
||||||
def usr1_handler(*unused):
|
def usr1_handler(*unused):
|
||||||
logging.info('Received USR1 singal. Restart process ...')
|
logging.info('Received USR1 singal. Restart process ...')
|
||||||
restart("MANU" if args.do_manual else "AUTO")
|
agent._restart("MANU" if args.do_manual else "AUTO")
|
||||||
|
|
||||||
signal.signal(signal.SIGUSR1, usr1_handler)
|
signal.signal(signal.SIGUSR1, usr1_handler)
|
||||||
|
|
||||||
|
147
builder/combined.json.pkr.hcl
Normal file
147
builder/combined.json.pkr.hcl
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
arm-image = {
|
||||||
|
source = "github.com/solo-io/arm-image"
|
||||||
|
version = ">= 0.0.1"
|
||||||
|
}
|
||||||
|
ansible = {
|
||||||
|
source = "github.com/hashicorp/ansible"
|
||||||
|
version = ">= 1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_hostname" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_version" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm-image" "rpi64-pwnagotchi" {
|
||||||
|
image_type = "raspberrypi"
|
||||||
|
iso_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz"
|
||||||
|
iso_checksum = "sha256:43d150e7901583919e4eb1f0fa83fe0363af2d1e9777a5bb707d696d535e2599"
|
||||||
|
output_filename = "../../../pwnagotchi-64bit.img"
|
||||||
|
qemu_binary = "qemu-aarch64-static"
|
||||||
|
target_image_size = 19969908736
|
||||||
|
image_mounts = ["/boot/firmware","/"]
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm-image" "rpi32-pwnagotchi" {
|
||||||
|
image_type = "raspberrypi"
|
||||||
|
iso_url = "https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-07-04/2024-07-04-raspios-bookworm-armhf-lite.img.xz"
|
||||||
|
iso_checksum = "sha256:df9c192d66d35e1ce67acde33a5b5f2b81ff02d2b986ea52f1f6ea211d646a1b"
|
||||||
|
output_filename = "../../../pwnagotchi-32bit.img"
|
||||||
|
qemu_binary = "qemu-arm-static"
|
||||||
|
qemu_args = ["-cpu", "arm1176"]
|
||||||
|
image_arch = "arm"
|
||||||
|
target_image_size = 19969908736
|
||||||
|
image_mounts = ["/boot/firmware","/"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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-image.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 "shell" {
|
||||||
|
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/local/src/pwnagotchi/"
|
||||||
|
source = "../"
|
||||||
|
}
|
||||||
|
|
||||||
|
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-image.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 = ["mkdir -p /usr/local/src/pwnagotchi"]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/local/src/pwnagotchi/"
|
||||||
|
source = "../"
|
||||||
|
}
|
||||||
|
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_file = "raspberrypi32.yml"
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ _show_complete()
|
|||||||
{
|
{
|
||||||
local cur opts node_names all_options opt_line
|
local cur opts node_names all_options opt_line
|
||||||
all_options="
|
all_options="
|
||||||
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --check-update --donate {plugins,google}
|
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 -h --help {list,install,enable,disable,uninstall,update,upgrade}
|
||||||
pwnagotchi plugins list -i --installed -h --help
|
pwnagotchi plugins list -i --installed -h --help
|
||||||
pwnagotchi plugins install -h --help
|
pwnagotchi plugins install -h --help
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
# Specifies amount of zram devices to create.
|
|
||||||
# By default, zramswap-start will use all available cores.
|
|
||||||
#CORES=1
|
|
||||||
|
|
||||||
# Specifies the amount of RAM that should be used for zram
|
|
||||||
# based on a percentage the total amount of available memory
|
|
||||||
PERCENTAGE=60
|
|
||||||
|
|
||||||
# Specifies a static amount of RAM that should be used for
|
|
||||||
# the ZRAM devices, this is in MiB
|
|
||||||
#ALLOCATION=256
|
|
||||||
|
|
||||||
# Specifies the priority for the swap devices, see swapon(2)
|
|
||||||
# for more details.
|
|
||||||
#PRIORITY=100
|
|
26
builder/data/etc/dphys-swapfile
Normal file
26
builder/data/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/etc/modules-load.d/modules.conf
Normal file
6
builder/data/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
|
16
builder/data/etc/rc.local
Normal file
16
builder/data/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/etc/systemd/system/bluetooth.service
Normal file
20
builder/data/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
|
@ -1,6 +1,6 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
|
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
|
||||||
Documentation=https://pwnagotchi.ai
|
Documentation=https://pwnagotchi.org
|
||||||
Wants=network.target
|
Wants=network.target
|
||||||
After=pwngrid-peer.service
|
After=pwngrid-peer.service
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
_hostname=$(hostname)
|
_hostname=$(hostname)
|
||||||
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.9/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
|
||||||
@ -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"
|
||||||
@ -30,4 +31,4 @@ echo
|
|||||||
echo " You can restart me using"
|
echo " You can restart me using"
|
||||||
echo " pwnkill"
|
echo " pwnkill"
|
||||||
echo
|
echo
|
||||||
echo " You learn more about me at https://pwnagotchi.ai/"
|
echo " You can learn more about me at https://pwnagotchi.org/"
|
||||||
|
@ -9,16 +9,6 @@ if is_crypted_mode; then
|
|||||||
done
|
done
|
||||||
fi
|
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 mon0
|
|
||||||
start_monitor_interface
|
start_monitor_interface
|
||||||
|
|
||||||
if is_auto_mode_no_delete; then
|
if is_auto_mode_no_delete; then
|
||||||
|
@ -10,7 +10,8 @@ if is_crypted_mode; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if is_auto_mode; then
|
if is_auto_mode; then
|
||||||
/usr/local/bin/pwnagotchi
|
/usr/local/src/pwnagotchi/env/bin/pwnagotchi
|
||||||
|
systemctl restart bettercap
|
||||||
else
|
else
|
||||||
/usr/local/bin/pwnagotchi --manual
|
/usr/local/src/pwnagotchi/env/bin/pwnagotchi --manual
|
||||||
fi
|
fi
|
||||||
|
@ -1,26 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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 mod
|
||||||
reload_brcm() {
|
reload_brcm() {
|
||||||
if ! modprobe -r brcmfmac; then
|
if ! modprobe -r brcmfmac; then
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
# This is not working quite yet
|
|
||||||
# https://github.com/mkaczanowski/packer-builder-arm/pull/172
|
|
||||||
packer {
|
|
||||||
required_plugins {
|
|
||||||
#arm = {
|
|
||||||
# version = "~> 1"
|
|
||||||
# source = "github.com/cdecoux/builder-arm"
|
|
||||||
#}
|
|
||||||
ansible = {
|
|
||||||
source = "github.com/hashicorp/ansible"
|
|
||||||
version = "~> 1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "pwn_hostname" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "pwn_version" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
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_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_checksum_type = "sha256"
|
|
||||||
file_target_extension = "xz"
|
|
||||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
|
||||||
image_path = "../../../pwnagotchi-rpi-bookworm-${var.pwn_version}-arm64.img"
|
|
||||||
qemu_binary_source_path = "/usr/bin/qemu-aarch64-static"
|
|
||||||
qemu_binary_destination_path = "/usr/bin/qemu-aarch64-static"
|
|
||||||
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 = "/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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/usr/bin/bettercap-launcher",
|
|
||||||
"data/usr/bin/hdmioff",
|
|
||||||
"data/usr/bin/hdmion",
|
|
||||||
"data/usr/bin/monstart",
|
|
||||||
"data/usr/bin/monstop",
|
|
||||||
"data/usr/bin/pwnagotchi-launcher",
|
|
||||||
"data/usr/bin/pwnlib",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
provisioner "shell" {
|
|
||||||
inline = ["chmod +x /usr/bin/*"]
|
|
||||||
}
|
|
||||||
|
|
||||||
provisioner "file" {
|
|
||||||
destination = "/etc/systemd/system/"
|
|
||||||
sources = [
|
|
||||||
"data/etc/systemd/system/bettercap.service",
|
|
||||||
"data/etc/systemd/system/pwnagotchi.service",
|
|
||||||
"data/etc/systemd/system/pwngrid-peer.service",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
provisioner "file" {
|
|
||||||
destination = "/etc/update-motd.d/01-motd"
|
|
||||||
source = "data/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"
|
|
||||||
}
|
|
||||||
}
|
|
82
builder/raspberrypi32.json.pkr.hcl
Normal file
82
builder/raspberrypi32.json.pkr.hcl
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
arm-image = {
|
||||||
|
source = "github.com/solo-io/arm-image"
|
||||||
|
version = ">= 0.0.1"
|
||||||
|
}
|
||||||
|
ansible = {
|
||||||
|
source = "github.com/hashicorp/ansible"
|
||||||
|
version = ">= 1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_hostname" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_version" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm-image" "rpi32-pwnagotchi" {
|
||||||
|
image_type = "raspberrypi"
|
||||||
|
iso_url = "https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-07-04/2024-07-04-raspios-bookworm-armhf-lite.img.xz"
|
||||||
|
iso_checksum = "sha256:df9c192d66d35e1ce67acde33a5b5f2b81ff02d2b986ea52f1f6ea211d646a1b"
|
||||||
|
output_filename = "../../../pwnagotchi-32bit.img"
|
||||||
|
qemu_binary = "qemu-arm-static"
|
||||||
|
qemu_args = ["-cpu", "arm1176"]
|
||||||
|
image_arch = "arm"
|
||||||
|
image_mounts = ["/boot/firmware","/"]
|
||||||
|
target_image_size = 19969908736
|
||||||
|
}
|
||||||
|
|
||||||
|
build {
|
||||||
|
name = "Raspberry Pi 32 Pwnagotchi"
|
||||||
|
sources = ["source.arm-image.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 "shell" {
|
||||||
|
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/local/src/pwnagotchi/"
|
||||||
|
source = "../"
|
||||||
|
}
|
||||||
|
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_file = "raspberrypi32.yml"
|
||||||
|
}
|
||||||
|
}
|
560
builder/raspberrypi32.yml
Normal file
560
builder/raspberrypi32.yml
Normal file
@ -0,0 +1,560 @@
|
|||||||
|
---
|
||||||
|
- hosts:
|
||||||
|
- 127.0.0.1
|
||||||
|
gather_facts: true
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
kernel:
|
||||||
|
min: "6.6"
|
||||||
|
full: "6.6.31+rpt-rpi-v6"
|
||||||
|
pwnagotchi:
|
||||||
|
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||||
|
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
|
||||||
|
services:
|
||||||
|
enable:
|
||||||
|
- bettercap.service
|
||||||
|
- fstrim.timer
|
||||||
|
- pwnagotchi.service
|
||||||
|
- pwngrid-peer.service
|
||||||
|
disable:
|
||||||
|
- apt-daily-upgrade.service
|
||||||
|
- apt-daily-upgrade.timer
|
||||||
|
- apt-daily.service
|
||||||
|
- apt-daily.timer
|
||||||
|
- bluetooth.service
|
||||||
|
- ifup@wlan0.service
|
||||||
|
packages:
|
||||||
|
caplets:
|
||||||
|
source: "https://github.com/jayofelony/caplets.git"
|
||||||
|
branch: "lite" # or master
|
||||||
|
bettercap:
|
||||||
|
source: "https://github.com/jayofelony/bettercap.git"
|
||||||
|
branch: "lite" # or master
|
||||||
|
pwngrid:
|
||||||
|
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+gita8e7c98-cp311-cp311-linux_armv6l.whl"
|
||||||
|
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/py0torch-bookworm-2024-05/torch-2.1.0a0+gita8e7c98-cp311-cp311-linux_armv6l.whl"
|
||||||
|
torchvision:
|
||||||
|
wheel: "torchvision-0.16.0+fbb4cc5-cp311-cp311-linux_armv6l.whl"
|
||||||
|
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/py0torch-bookworm-2024-05/torchvision-0.16.0+fbb4cc5-cp311-cp311-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-dbg
|
||||||
|
- libpcap0.8-dev
|
||||||
|
remove:
|
||||||
|
- nfs-common
|
||||||
|
- triggerhappy
|
||||||
|
install:
|
||||||
|
- aircrack-ng
|
||||||
|
- autoconf
|
||||||
|
- bison
|
||||||
|
- bluez
|
||||||
|
- bluez-tools
|
||||||
|
- build-essential
|
||||||
|
- curl
|
||||||
|
- dphys-swapfile
|
||||||
|
- fbi
|
||||||
|
- firmware-atheros
|
||||||
|
- firmware-brcm80211
|
||||||
|
- firmware-libertas
|
||||||
|
- firmware-misc-nonfree
|
||||||
|
- firmware-realtek
|
||||||
|
- flex
|
||||||
|
- g++
|
||||||
|
- gawk
|
||||||
|
- gcc-arm-none-eabi
|
||||||
|
- git
|
||||||
|
- libatlas-base-dev
|
||||||
|
- libc6-dev
|
||||||
|
- libcpuinfo-dev
|
||||||
|
- libcurl-ocaml-dev
|
||||||
|
- libdbus-1-dev
|
||||||
|
- libdbus-glib-1-dev
|
||||||
|
- libfl-dev
|
||||||
|
- libgmp3-dev
|
||||||
|
- libnetfilter-queue-dev
|
||||||
|
- libopenblas-dev # https://stackoverflow.com/questions/14570011/explain-why-numpy-should-not-be-imported-from-source-directory
|
||||||
|
- libopenjp2-7
|
||||||
|
- libpcap-dev
|
||||||
|
#- libraspberrypi-bin ## seems to be provided by raspi-utils now
|
||||||
|
- libraspberrypi-dev
|
||||||
|
- libraspberrypi-doc
|
||||||
|
- libraspberrypi0
|
||||||
|
- libsleef-dev
|
||||||
|
- libssl-dev
|
||||||
|
- libssl-ocaml-dev
|
||||||
|
- libtool
|
||||||
|
- libusb-1.0-0-dev
|
||||||
|
- make
|
||||||
|
- ntp
|
||||||
|
- pkg-config
|
||||||
|
- python3-dev
|
||||||
|
- python3-pip
|
||||||
|
- python3-protobuf
|
||||||
|
- python3-setuptools
|
||||||
|
- python3-smbus
|
||||||
|
- qpdf
|
||||||
|
- raspberrypi-kernel-headers
|
||||||
|
- rsync
|
||||||
|
- tcpdump
|
||||||
|
- texinfo
|
||||||
|
- unzip
|
||||||
|
- wget
|
||||||
|
- wl
|
||||||
|
- xxd
|
||||||
|
- zlib1g-dev
|
||||||
|
environment:
|
||||||
|
ARCHFLAGS: "-arch armv6l"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
# First we install packages
|
||||||
|
- name: install packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages.apt.install }}"
|
||||||
|
state: latest
|
||||||
|
update_cache: yes
|
||||||
|
install_recommends: no
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- name: install 32bit torch
|
||||||
|
shell: "python3 -m pip install {{ packages.torch.url }} {{ packages.torchvision.url }} --break-system-packages"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch armv6l"
|
||||||
|
|
||||||
|
# Now we set up /boot/firmware
|
||||||
|
- name: Create pi user
|
||||||
|
copy:
|
||||||
|
dest: /boot/firmware/userconf
|
||||||
|
content: |
|
||||||
|
pi:$5$733Efsksay$SEFUKemv8FaNAu6X4GUfxdSzSDh6PbpOcdtNe5b7Nt0
|
||||||
|
|
||||||
|
- name: enable ssh on boot
|
||||||
|
file:
|
||||||
|
path: /boot/firmware/ssh
|
||||||
|
state: touch
|
||||||
|
|
||||||
|
- name: remove current rc.local
|
||||||
|
file:
|
||||||
|
path: /etc/rc.local
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: change root partition
|
||||||
|
replace:
|
||||||
|
dest: /boot/firmware/cmdline.txt
|
||||||
|
backup: no
|
||||||
|
regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+"
|
||||||
|
replace: "root=/dev/mmcblk0p2"
|
||||||
|
|
||||||
|
- name: configure /boot/firmware/cmdline.txt
|
||||||
|
lineinfile:
|
||||||
|
path: /boot/firmware/cmdline.txt
|
||||||
|
backrefs: True
|
||||||
|
state: present
|
||||||
|
backup: no
|
||||||
|
regexp: '(.*)$'
|
||||||
|
line: '\1 modules-load=dwc2,g_ether'
|
||||||
|
|
||||||
|
- name: setup /boot/firmware/config.txt
|
||||||
|
blockinfile:
|
||||||
|
path: /boot/firmware/config.txt
|
||||||
|
insertafter: EOF
|
||||||
|
block: |
|
||||||
|
dtparam=i2c1=on
|
||||||
|
dtparam=i2c_arm=on
|
||||||
|
dtparam=spi=on
|
||||||
|
gpu_mem=1
|
||||||
|
dtoverlay=dwc2
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
enable_uart=1
|
||||||
|
|
||||||
|
[pi0]
|
||||||
|
dtoverlay=spi0-2cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
# Now we disable sap and a2dp, we don't use them on rpi
|
||||||
|
- name: disable sap plugin for bluetooth.service
|
||||||
|
lineinfile:
|
||||||
|
dest: /lib/systemd/system/bluetooth.service
|
||||||
|
regexp: '^ExecStart=/usr/libexec/bluetooth/bluetoothd$'
|
||||||
|
line: 'ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp'
|
||||||
|
state: present
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
#
|
||||||
|
# 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 latest hcxtools
|
||||||
|
- name: clone hcxtools
|
||||||
|
git:
|
||||||
|
repo: https://github.com/ZerBea/hcxtools.git
|
||||||
|
dest: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
- name: install hcxtools
|
||||||
|
shell: "make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
- name: remove hcxtools directory
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
# Installing nexmon
|
||||||
|
- 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/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch armv6l"
|
||||||
|
|
||||||
|
- name: make firmware patch (bcm43430a1)
|
||||||
|
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch armv6l"
|
||||||
|
|
||||||
|
- name: install new firmware (bcm43430a1)
|
||||||
|
copy:
|
||||||
|
src: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/brcmfmac43430-sdio.bin
|
||||||
|
dest: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||||
|
follow: true
|
||||||
|
|
||||||
|
- name: copy modified driver
|
||||||
|
copy:
|
||||||
|
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||||
|
dest: "/usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch armv6l"
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- name: backup original driver
|
||||||
|
command: "mv /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||||
|
|
||||||
|
- name: load brcmfmac drivers
|
||||||
|
command: "/sbin/depmod {{ kernel.full }}"
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
|
||||||
|
# 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: Create custom config directory
|
||||||
|
file:
|
||||||
|
path: /etc/pwnagotchi/conf.d/
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
#- name: clone pwnagotchi repository
|
||||||
|
# git:
|
||||||
|
# repo: https://github.com/jayofelony/pwnagotchi.git
|
||||||
|
# dest: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
|
- name: build pwnagotchi wheel
|
||||||
|
command: "pip3 install . --no-cache-dir --break-system-packages"
|
||||||
|
args:
|
||||||
|
chdir: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
|
- name: create /usr/local/share/pwnagotchi/ folder
|
||||||
|
file:
|
||||||
|
path: /usr/local/share/pwnagotchi/
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: Create custom plugin directory
|
||||||
|
file:
|
||||||
|
path: /usr/local/share/pwnagotchi/custom-plugins/
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: remove pwnagotchi folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
#
|
||||||
|
# pwngrid, bettercap
|
||||||
|
#
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
- name: Install go-1.21
|
||||||
|
unarchive:
|
||||||
|
src: https://go.dev/dl/go1.22.3.linux-armv6l.tar.gz
|
||||||
|
dest: /usr/local
|
||||||
|
remote_src: yes
|
||||||
|
register: golang
|
||||||
|
|
||||||
|
- name: Update .bashrc for go-1.21
|
||||||
|
blockinfile:
|
||||||
|
dest: /etc/profile
|
||||||
|
state: present
|
||||||
|
block: |
|
||||||
|
export GOPATH=$HOME/go
|
||||||
|
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
||||||
|
when: golang.changed
|
||||||
|
|
||||||
|
- name: download pwngrid
|
||||||
|
git:
|
||||||
|
repo: "{{ packages.pwngrid.source }}"
|
||||||
|
dest: /usr/local/src/pwngrid
|
||||||
|
|
||||||
|
- name: install pwngrid
|
||||||
|
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/pwngrid
|
||||||
|
|
||||||
|
- name: remove pwngrid folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/pwngrid
|
||||||
|
|
||||||
|
- name: download bettercap
|
||||||
|
git:
|
||||||
|
repo: "{{ packages.bettercap.source }}"
|
||||||
|
version: "{{ packages.bettercap.branch }} "
|
||||||
|
dest: /usr/local/src/bettercap
|
||||||
|
|
||||||
|
- name: install bettercap 2.32.4
|
||||||
|
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/bettercap
|
||||||
|
|
||||||
|
- name: remove bettercap folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/bettercap
|
||||||
|
|
||||||
|
- name: clone bettercap caplets
|
||||||
|
git:
|
||||||
|
repo: "{{ packages.caplets.source }}"
|
||||||
|
version: "{{ packages.caplets.branch }}"
|
||||||
|
dest: /tmp/caplets
|
||||||
|
register: capletsgit
|
||||||
|
|
||||||
|
- name: install bettercap caplets
|
||||||
|
make:
|
||||||
|
chdir: /tmp/caplets
|
||||||
|
target: install
|
||||||
|
when: capletsgit.changed
|
||||||
|
|
||||||
|
- name: create /etc/pwnagotchi folder
|
||||||
|
file:
|
||||||
|
path: /etc/pwnagotchi
|
||||||
|
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
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /etc/motd
|
||||||
|
|
||||||
|
- name: Delete motd 10-uname
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /etc/update-motd.d/10-uname
|
||||||
|
|
||||||
|
- 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 }}"
|
||||||
|
register: enabled
|
||||||
|
|
||||||
|
- 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 pre-collected packages zip
|
||||||
|
file:
|
||||||
|
path: /root/go_pkgs.tgz
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: remove /root/go folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /root/go
|
||||||
|
|
||||||
|
- name: remove /usr/local/go folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/go
|
||||||
|
|
||||||
|
- name: remove pip cache
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /root/.cache/pip
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
# Now we remove packages
|
||||||
|
- name: remove unnecessary apt packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages.apt.remove }}"
|
||||||
|
state: absent
|
||||||
|
purge: yes
|
||||||
|
register: removed
|
||||||
|
|
||||||
|
- name: remove dependencies that are no longer required
|
||||||
|
apt:
|
||||||
|
autoremove: yes
|
||||||
|
when: removed.changed
|
||||||
|
|
||||||
|
- name: install rpi-sys-mods again?
|
||||||
|
apt:
|
||||||
|
state: present
|
||||||
|
name: raspberrypi-sys-mods
|
||||||
|
update_cache: yes
|
||||||
|
install_recommends: no
|
||||||
|
|
||||||
|
- name: clean apt cache
|
||||||
|
apt:
|
||||||
|
autoclean: true
|
||||||
|
when: removed.changed
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: reload systemd services
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
|
when: enabled.changed
|
84
builder/raspberrypi64.json.pkr.hcl
Normal file
84
builder/raspberrypi64.json.pkr.hcl
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
arm-image = {
|
||||||
|
source = "github.com/solo-io/arm-image"
|
||||||
|
version = ">= 0.0.1"
|
||||||
|
}
|
||||||
|
ansible = {
|
||||||
|
source = "github.com/hashicorp/ansible"
|
||||||
|
version = ">= 1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_hostname" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_version" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm-image" "rpi64-pwnagotchi" {
|
||||||
|
image_type = "raspberrypi"
|
||||||
|
iso_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz"
|
||||||
|
iso_checksum = "sha256:43d150e7901583919e4eb1f0fa83fe0363af2d1e9777a5bb707d696d535e2599"
|
||||||
|
output_filename = "../../../pwnagotchi-64bit.img"
|
||||||
|
qemu_binary = "qemu-aarch64-static"
|
||||||
|
target_image_size = 19969908736
|
||||||
|
image_mounts = ["/boot/firmware","/"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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-image.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 "shell" {
|
||||||
|
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/local/src/pwnagotchi/"
|
||||||
|
source = "../"
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
@ -5,28 +5,18 @@
|
|||||||
become: true
|
become: true
|
||||||
vars:
|
vars:
|
||||||
kernel:
|
kernel:
|
||||||
min: "6.1"
|
min: "6.6"
|
||||||
full: "6.1.0-rpi7-rpi-v8"
|
full: "6.6.31+rpt-rpi-v8"
|
||||||
|
full_pi5: "6.6.31+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
|
||||||
- fstrim.timer
|
- fstrim.timer
|
||||||
- pwnagotchi.service
|
- pwnagotchi.service
|
||||||
- pwngrid-peer.service
|
- pwngrid-peer.service
|
||||||
- zramswap.service
|
|
||||||
disable:
|
disable:
|
||||||
- apt-daily-upgrade.service
|
- apt-daily-upgrade.service
|
||||||
- apt-daily-upgrade.timer
|
- apt-daily-upgrade.timer
|
||||||
@ -37,10 +27,11 @@
|
|||||||
packages:
|
packages:
|
||||||
caplets:
|
caplets:
|
||||||
source: "https://github.com/jayofelony/caplets.git"
|
source: "https://github.com/jayofelony/caplets.git"
|
||||||
|
branch: "lite" # or master
|
||||||
bettercap:
|
bettercap:
|
||||||
source: "https://github.com/jayofelony/bettercap.git"
|
source: "https://github.com/jayofelony/bettercap.git"
|
||||||
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.2/bettercap-2.32.2.zip"
|
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.4/bettercap-2.32.4.zip"
|
||||||
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
branch: "lite" # or master
|
||||||
pwngrid:
|
pwngrid:
|
||||||
source: "https://github.com/jayofelony/pwngrid.git"
|
source: "https://github.com/jayofelony/pwngrid.git"
|
||||||
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.5/pwngrid-1.10.5-aarch64.zip"
|
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.5/pwngrid-1.10.5-aarch64.zip"
|
||||||
@ -51,151 +42,117 @@
|
|||||||
- libpcap0.8-dev_1.9.1-4_arm64.deb
|
- libpcap0.8-dev_1.9.1-4_arm64.deb
|
||||||
- libpcap0.8_1.9.1-4_arm64.deb
|
- libpcap0.8_1.9.1-4_arm64.deb
|
||||||
hold:
|
hold:
|
||||||
- libpcap-dev
|
|
||||||
- libpcap0.8
|
|
||||||
- libpcap0.8-dev
|
|
||||||
- libpcap0.8-dbg
|
|
||||||
remove:
|
|
||||||
- avahi-daemon
|
|
||||||
- dhpys-swapfile
|
|
||||||
- nfs-common
|
|
||||||
- triggerhappy
|
|
||||||
- wpasupplicant
|
|
||||||
install:
|
|
||||||
- aircrack-ng
|
|
||||||
- autoconf
|
|
||||||
- bc
|
|
||||||
- bison
|
|
||||||
- bluez
|
|
||||||
- build-essential
|
|
||||||
- curl
|
|
||||||
- dkms
|
|
||||||
- fbi
|
|
||||||
- flex
|
|
||||||
- fonts-dejavu
|
|
||||||
- fonts-dejavu-core
|
|
||||||
- fonts-dejavu-extra
|
|
||||||
- fonts-freefont-ttf
|
|
||||||
- g++
|
|
||||||
- gawk
|
|
||||||
- gcc-arm-none-eabi
|
|
||||||
- git
|
|
||||||
- libatlas-base-dev
|
|
||||||
- libavcodec59
|
|
||||||
- libavformat59
|
|
||||||
- libblas-dev
|
|
||||||
- libbluetooth-dev
|
|
||||||
- libbz2-dev
|
|
||||||
- libc-ares-dev
|
|
||||||
- libc6-dev
|
|
||||||
- libcap-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
|
|
||||||
- libraspberrypi-bin
|
|
||||||
- libraspberrypi-dev
|
|
||||||
- libraspberrypi-doc
|
|
||||||
- libraspberrypi0
|
|
||||||
- libsqlite3-dev
|
|
||||||
- libssl-dev
|
|
||||||
- libswscale5
|
|
||||||
- libtiff6
|
|
||||||
- libtool
|
|
||||||
- libusb-1.0-0-dev
|
|
||||||
- lsof
|
|
||||||
- make
|
|
||||||
- python3-yaml
|
|
||||||
- python3-dbus
|
|
||||||
- python3-flask
|
|
||||||
- python3-flask-cors
|
|
||||||
- python3-flaskext.wtf
|
|
||||||
- python3-gast
|
|
||||||
- python3-pil
|
|
||||||
- python3-pycryptodome
|
|
||||||
- python3-requests
|
|
||||||
- python3-scapy
|
|
||||||
- python3-smbus2
|
|
||||||
- python3-spidev
|
|
||||||
- python3-tweepy
|
|
||||||
- python3-werkzeug
|
|
||||||
- firmware-atheros
|
- firmware-atheros
|
||||||
- firmware-brcm80211
|
- firmware-brcm80211
|
||||||
- firmware-libertas
|
- firmware-libertas
|
||||||
- firmware-misc-nonfree
|
- firmware-misc-nonfree
|
||||||
- firmware-realtek
|
- firmware-realtek
|
||||||
|
- libpcap-dev
|
||||||
|
- libpcap0.8
|
||||||
|
- libpcap0.8-dbg
|
||||||
|
- libpcap0.8-dev
|
||||||
|
remove:
|
||||||
|
- dhpys-swapfile
|
||||||
|
- nfs-common
|
||||||
|
- triggerhappy
|
||||||
|
install:
|
||||||
|
- aircrack-ng
|
||||||
|
- autoconf
|
||||||
|
- bison
|
||||||
|
- bluez
|
||||||
|
- bluez-tools
|
||||||
|
- build-essential
|
||||||
|
- curl
|
||||||
|
- dphys-swapfile
|
||||||
|
- fbi
|
||||||
|
- firmware-atheros
|
||||||
|
- firmware-brcm80211
|
||||||
|
- firmware-libertas
|
||||||
|
- firmware-misc-nonfree
|
||||||
|
- firmware-realtek
|
||||||
|
- flex
|
||||||
|
- g++
|
||||||
|
- gawk
|
||||||
|
- gcc-arm-none-eabi
|
||||||
|
- git
|
||||||
|
- libc6-dev
|
||||||
|
- libcurl-ocaml-dev
|
||||||
|
- libdbus-1-dev
|
||||||
|
- libdbus-glib-1-dev
|
||||||
|
- libfl-dev
|
||||||
|
- libgmp3-dev
|
||||||
|
- libnetfilter-queue-dev
|
||||||
|
- libpcap-dev
|
||||||
|
#- libraspberrypi-bin ## seems to be provided by raspi-utils now
|
||||||
|
- libraspberrypi-dev
|
||||||
|
- libraspberrypi-doc
|
||||||
|
- libraspberrypi0
|
||||||
|
- libssl-dev
|
||||||
|
- libssl-ocaml-dev
|
||||||
|
- libtool
|
||||||
|
- libusb-1.0-0-dev
|
||||||
|
- make
|
||||||
|
- ntp
|
||||||
|
- pkg-config
|
||||||
|
- python3-dev
|
||||||
- python3-pip
|
- python3-pip
|
||||||
- python3-setuptools
|
- python3-setuptools
|
||||||
- python3-smbus
|
- python3-smbus
|
||||||
- qpdf
|
- qpdf
|
||||||
- raspberrypi-kernel-headers
|
- raspberrypi-kernel-headers
|
||||||
|
- raspberrypi-sys-mods
|
||||||
- rsync
|
- rsync
|
||||||
- screen
|
|
||||||
- tcpdump
|
- tcpdump
|
||||||
- texinfo
|
- texinfo
|
||||||
- time
|
|
||||||
- tk-dev
|
|
||||||
- unzip
|
- unzip
|
||||||
- vim
|
|
||||||
- wget
|
- wget
|
||||||
- wl
|
- wl
|
||||||
- xxd
|
- xxd
|
||||||
- zlib1g-dev
|
- zlib1g-dev
|
||||||
- zram-tools
|
|
||||||
environment:
|
environment:
|
||||||
ARCHFLAGS: "-arch aarch64"
|
ARCHFLAGS: "-arch aarch64"
|
||||||
QEMU_UNAME: "{{ kernel.full }}"
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
# First we install and remove unnecessary packages
|
# First we install packages
|
||||||
- name: install packages
|
- name: install packages
|
||||||
apt:
|
apt:
|
||||||
name: "{{ packages.apt.install }}"
|
name: "{{ packages.apt.install }}"
|
||||||
state: present
|
state: latest
|
||||||
update_cache: yes
|
update_cache: yes
|
||||||
install_recommends: false
|
install_recommends: no
|
||||||
|
|
||||||
- name: remove unnecessary apt packages
|
- name: update pip3, setuptools, wheel
|
||||||
apt:
|
shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages"
|
||||||
name: "{{ packages.apt.remove }}"
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src
|
||||||
|
|
||||||
|
- name: build pwnagotchi wheel
|
||||||
|
command: "pip3 install . --no-cache-dir --break-system-packages"
|
||||||
|
args:
|
||||||
|
chdir: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
|
- name: remove pwnagotchi folder
|
||||||
|
file:
|
||||||
state: absent
|
state: absent
|
||||||
purge: yes
|
path: /usr/local/src/pwnagotchi
|
||||||
register: removed
|
|
||||||
|
|
||||||
# Now we set up /boot/firmware
|
# Now we set up /boot/firmware
|
||||||
- name: Create pi user
|
- name: Create pi user
|
||||||
copy:
|
copy:
|
||||||
dest: /boot/firmware/userconf
|
dest: /boot/firmware/userconf
|
||||||
content: |
|
content: |
|
||||||
pi:$6$3jNr0GA9KIyt4hmM$efeVIopdMQ8DGgEPCWWlbx3mJJNAYci1lEXGdlky0xPyjqwKNbwTL5SrCcpb4144C4IvzWjn7Iv.QjqmU7iyT/
|
pi:$5$733Efsksay$SEFUKemv8FaNAu6X4GUfxdSzSDh6PbpOcdtNe5b7Nt0
|
||||||
|
|
||||||
- name: enable ssh on boot
|
- name: enable ssh on boot
|
||||||
file:
|
file:
|
||||||
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:
|
||||||
@ -213,6 +170,35 @@
|
|||||||
regexp: '(.*)$'
|
regexp: '(.*)$'
|
||||||
line: '\1 modules-load=dwc2,g_ether'
|
line: '\1 modules-load=dwc2,g_ether'
|
||||||
|
|
||||||
|
- name: setup /boot/firmware/config.txt
|
||||||
|
blockinfile:
|
||||||
|
path: /boot/firmware/config.txt
|
||||||
|
insertafter: EOF
|
||||||
|
block: |
|
||||||
|
dtparam=i2c1=on
|
||||||
|
dtparam=i2c_arm=on
|
||||||
|
dtparam=spi=on
|
||||||
|
gpu_mem=1
|
||||||
|
dtoverlay=dwc2
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
enable_uart=1
|
||||||
|
|
||||||
|
[pi02w]
|
||||||
|
dtoverlay=spi0-2cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi3]
|
||||||
|
dtoverlay=spi0-2cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi4]
|
||||||
|
dtoverlay=spi0-2cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi5]
|
||||||
|
dtoverlay=spi0-2cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
- name: change hostname
|
- name: change hostname
|
||||||
lineinfile:
|
lineinfile:
|
||||||
dest: /etc/hostname
|
dest: /etc/hostname
|
||||||
@ -271,35 +257,108 @@
|
|||||||
dest: /usr/local/lib/libpcap.so.0.8
|
dest: /usr/local/lib/libpcap.so.0.8
|
||||||
state: link
|
state: link
|
||||||
|
|
||||||
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
# install latest hcxtools
|
||||||
|
- name: clone hcxtools
|
||||||
|
git:
|
||||||
|
repo: https://github.com/ZerBea/hcxtools.git
|
||||||
|
dest: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
- name: install hcxtools
|
||||||
|
shell: "make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
- name: remove hcxtools directory
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
# Installing nexmon
|
||||||
- name: clone nexmon repository
|
- name: clone nexmon repository
|
||||||
git:
|
git:
|
||||||
repo: https://github.com/DrSchottky/nexmon.git
|
repo: https://github.com/DrSchottky/nexmon.git
|
||||||
dest: /usr/local/src/nexmon
|
dest: /usr/local/src/nexmon
|
||||||
|
|
||||||
- name: make firmware
|
# FIRST WE BUILD DRIVER FOR RPi5
|
||||||
|
- name: make firmware, RPi5
|
||||||
shell: "source ./setup_env.sh && make"
|
shell: "source ./setup_env.sh && make"
|
||||||
args:
|
args:
|
||||||
executable: /bin/bash
|
executable: /bin/bash
|
||||||
chdir: /usr/local/src/nexmon/
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
- name: make firmware patch (bcm43455c0)
|
- name: make firmware patch (bcm43455c0), RPi5
|
||||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
|
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
|
||||||
args:
|
args:
|
||||||
executable: /bin/bash
|
executable: /bin/bash
|
||||||
chdir: /usr/local/src/nexmon/
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
- name: install new firmware (bcm43455c0)
|
- name: copy modified driver, RPi5
|
||||||
|
copy:
|
||||||
|
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||||
|
dest: "/usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
- name: backup original driver, RPi5
|
||||||
|
command: "mv /usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||||
|
|
||||||
|
- name: load brcmfmac drivers
|
||||||
|
command: "/sbin/depmod {{ kernel.full_pi5 }}"
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||||
|
|
||||||
|
- name: Delete nexmon content & directory
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/nexmon/
|
||||||
|
|
||||||
|
# NOW WE BUILD DRIVERS FOR RPi4, RPizero2w and RPi3
|
||||||
|
- name: clone nexmon repository
|
||||||
|
git:
|
||||||
|
repo: https://github.com/DrSchottky/nexmon.git
|
||||||
|
dest: /usr/local/src/nexmon
|
||||||
|
|
||||||
|
- name: make firmware, RPi4
|
||||||
|
shell: "source ./setup_env.sh && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
- name: make firmware patch (bcm43455c0), RPi4
|
||||||
|
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
- name: install new firmware (bcm43455c0), RPi4 RPi5
|
||||||
copy:
|
copy:
|
||||||
src: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/brcmfmac43455-sdio.bin
|
src: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/brcmfmac43455-sdio.bin
|
||||||
dest: /usr/lib/firmware/brcm/brcmfmac43455-sdio.bin
|
dest: /usr/lib/firmware/brcm/brcmfmac43455-sdio.bin
|
||||||
follow: true
|
follow: true
|
||||||
|
|
||||||
|
# NOW WE BUILD DRIVERS FOR RPiZero2W, RPi 3
|
||||||
- name: make firmware patch (bcm43436b0)
|
- name: make firmware patch (bcm43436b0)
|
||||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make"
|
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make"
|
||||||
args:
|
args:
|
||||||
executable: /bin/bash
|
executable: /bin/bash
|
||||||
chdir: /usr/local/src/nexmon/
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
- name: install new firmware (bcm43436b0)
|
- name: install new firmware (bcm43436b0)
|
||||||
copy:
|
copy:
|
||||||
@ -312,6 +371,9 @@
|
|||||||
args:
|
args:
|
||||||
executable: /bin/bash
|
executable: /bin/bash
|
||||||
chdir: /usr/local/src/nexmon/
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
- name: install new firmware (bcm43430a1)
|
- name: install new firmware (bcm43430a1)
|
||||||
copy:
|
copy:
|
||||||
@ -319,6 +381,14 @@
|
|||||||
dest: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
dest: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||||
follow: true
|
follow: true
|
||||||
|
|
||||||
|
- name: copy modified driver, RPi4
|
||||||
|
copy:
|
||||||
|
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||||
|
dest: "/usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
|
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
|
||||||
copy:
|
copy:
|
||||||
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||||
@ -338,17 +408,15 @@
|
|||||||
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.clm_blob
|
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.clm_blob
|
||||||
- /usr/lib/firmware/brcm/brcmfmac43436-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/brcmfmac43455-sdio.clm_blob
|
- /usr/lib/firmware/brcm/brcmfmac43455-sdio.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/BCM43430A1.raspberrypi,model-zero-2-w.hcd
|
||||||
|
|
||||||
- name: backup original driver
|
- name: backup original driver
|
||||||
command: "mv /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
command: "mv /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||||
|
|
||||||
- name: copy modified driver
|
- name: load brcmfmac drivers
|
||||||
copy:
|
command: "/sbin/depmod {{ kernel.full }}"
|
||||||
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
environment:
|
||||||
dest: "/usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
|
||||||
- name : load brcmfmac drivers
|
|
||||||
command: "/sbin/depmod -a"
|
|
||||||
|
|
||||||
# To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation
|
# To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation
|
||||||
- name: Delete nexmon content & directory
|
- name: Delete nexmon content & directory
|
||||||
@ -356,46 +424,31 @@
|
|||||||
state: absent
|
state: absent
|
||||||
path: /usr/local/src/nexmon/
|
path: /usr/local/src/nexmon/
|
||||||
|
|
||||||
- name: Create custom plugin directory
|
|
||||||
file:
|
|
||||||
path: /usr/local/share/pwnagotchi/custom-plugins/
|
|
||||||
state: directory
|
|
||||||
|
|
||||||
- name: Create custom config directory
|
- name: Create custom config directory
|
||||||
file:
|
file:
|
||||||
path: /etc/pwnagotchi/conf.d/
|
path: /etc/pwnagotchi/conf.d/
|
||||||
state: directory
|
state: directory
|
||||||
|
|
||||||
- name: clone pwnagotchi repository
|
|
||||||
git:
|
|
||||||
repo: https://github.com/jayofelony/pwnagotchi-bookworm.git
|
|
||||||
dest: /usr/local/src/pwnagotchi
|
|
||||||
|
|
||||||
- name: build pwnagotchi wheel
|
|
||||||
command: "pip3 install . --no-cache-dir --break-system-packages"
|
|
||||||
args:
|
|
||||||
chdir: /usr/local/src/pwnagotchi
|
|
||||||
|
|
||||||
- name: remove pwnagotchi folder
|
|
||||||
file:
|
|
||||||
state: absent
|
|
||||||
path: /usr/local/src/pwnagotchi
|
|
||||||
|
|
||||||
- name: create /usr/local/share/pwnagotchi/ folder
|
- name: create /usr/local/share/pwnagotchi/ folder
|
||||||
file:
|
file:
|
||||||
path: /usr/local/share/pwnagotchi/
|
path: /usr/local/share/pwnagotchi/
|
||||||
state: directory
|
state: directory
|
||||||
|
|
||||||
|
- name: Create custom plugin directory
|
||||||
|
file:
|
||||||
|
path: /usr/local/share/pwnagotchi/custom-plugins/
|
||||||
|
state: directory
|
||||||
|
|
||||||
- name: Install go-1.21
|
- name: Install go-1.21
|
||||||
unarchive:
|
unarchive:
|
||||||
src: https://go.dev/dl/go1.21.5.linux-arm64.tar.gz
|
src: https://go.dev/dl/go1.22.3.linux-arm64.tar.gz
|
||||||
dest: /usr/local
|
dest: /usr/local
|
||||||
remote_src: yes
|
remote_src: yes
|
||||||
register: golang
|
register: golang
|
||||||
|
|
||||||
- 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
|
||||||
@ -421,9 +474,10 @@
|
|||||||
- name: download bettercap
|
- name: download bettercap
|
||||||
git:
|
git:
|
||||||
repo: "{{ packages.bettercap.source }}"
|
repo: "{{ packages.bettercap.source }}"
|
||||||
|
version: "{{ packages.bettercap.branch }}"
|
||||||
dest: /usr/local/src/bettercap
|
dest: /usr/local/src/bettercap
|
||||||
|
|
||||||
- name: install bettercap 2.32.2
|
- name: install bettercap 2.32.4
|
||||||
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
||||||
args:
|
args:
|
||||||
executable: /bin/bash
|
executable: /bin/bash
|
||||||
@ -447,6 +501,7 @@
|
|||||||
- name: clone bettercap caplets
|
- name: clone bettercap caplets
|
||||||
git:
|
git:
|
||||||
repo: "{{ packages.caplets.source }}"
|
repo: "{{ packages.caplets.source }}"
|
||||||
|
version: "{{ packages.caplets.branch }}"
|
||||||
dest: /tmp/caplets
|
dest: /tmp/caplets
|
||||||
register: capletsgit
|
register: capletsgit
|
||||||
|
|
||||||
@ -456,31 +511,11 @@
|
|||||||
target: install
|
target: install
|
||||||
when: capletsgit.changed
|
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
|
- name: create /etc/pwnagotchi folder
|
||||||
file:
|
file:
|
||||||
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
|
||||||
@ -493,7 +528,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
|
||||||
@ -506,24 +541,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 }}"
|
||||||
@ -557,11 +574,6 @@
|
|||||||
group: pi
|
group: pi
|
||||||
recurse: true
|
recurse: true
|
||||||
|
|
||||||
- name: clean apt cache
|
|
||||||
apt:
|
|
||||||
autoclean: true
|
|
||||||
when: removed.changed
|
|
||||||
|
|
||||||
- name: remove pre-collected packages zip
|
- name: remove pre-collected packages zip
|
||||||
file:
|
file:
|
||||||
path: /root/go_pkgs.tgz
|
path: /root/go_pkgs.tgz
|
||||||
@ -582,11 +594,6 @@
|
|||||||
state: absent
|
state: absent
|
||||||
path: /root/.cache/pip
|
path: /root/.cache/pip
|
||||||
|
|
||||||
- name: remove dependencies that are no longer required
|
|
||||||
apt:
|
|
||||||
autoremove: yes
|
|
||||||
when: removed.changed
|
|
||||||
|
|
||||||
- name: remove ssh keys
|
- name: remove ssh keys
|
||||||
file:
|
file:
|
||||||
state: absent
|
state: absent
|
||||||
@ -599,6 +606,24 @@
|
|||||||
args:
|
args:
|
||||||
executable: /bin/bash
|
executable: /bin/bash
|
||||||
|
|
||||||
|
# Now we remove packages
|
||||||
|
- name: remove unnecessary apt packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages.apt.remove }}"
|
||||||
|
state: absent
|
||||||
|
purge: yes
|
||||||
|
register: removed
|
||||||
|
|
||||||
|
- name: remove dependencies that are no longer required
|
||||||
|
apt:
|
||||||
|
autoremove: yes
|
||||||
|
when: removed.changed
|
||||||
|
|
||||||
|
- name: clean apt cache
|
||||||
|
apt:
|
||||||
|
autoclean: true
|
||||||
|
when: removed.changed
|
||||||
|
|
||||||
handlers:
|
handlers:
|
||||||
- name: reload systemd services
|
- name: reload systemd services
|
||||||
systemd:
|
systemd:
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = '2.7.3'
|
__version__ = '2.9.2'
|
||||||
|
@ -4,7 +4,9 @@ import os
|
|||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
import _thread
|
#import _thread
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
|
||||||
import pwnagotchi
|
import pwnagotchi
|
||||||
import pwnagotchi.utils as utils
|
import pwnagotchi.utils as utils
|
||||||
@ -21,17 +23,17 @@ RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
|
|||||||
|
|
||||||
class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||||
def __init__(self, view, config, keypair):
|
def __init__(self, view, config, keypair):
|
||||||
Client.__init__(self, config['bettercap']['hostname'],
|
Client.__init__(self,
|
||||||
config['bettercap']['scheme'],
|
"127.0.0.1" if "hostname" not in config['bettercap'] else config['bettercap']['hostname'],
|
||||||
config['bettercap']['port'],
|
"http" if "scheme" not in config['bettercap'] else config['bettercap']['scheme'],
|
||||||
config['bettercap']['username'],
|
8081 if "port" not in config['bettercap'] else config['bettercap']['port'],
|
||||||
config['bettercap']['password'])
|
"pwnagotchi" if "username" not in config['bettercap'] else config['bettercap']['username'],
|
||||||
|
"pwnagotchi" if "password" not in config['bettercap'] else config['bettercap']['password'])
|
||||||
Automata.__init__(self, config, view)
|
Automata.__init__(self, config, view)
|
||||||
AsyncAdvertiser.__init__(self, config, view, keypair)
|
AsyncAdvertiser.__init__(self, config, view, keypair)
|
||||||
AsyncTrainer.__init__(self, config)
|
AsyncTrainer.__init__(self, config)
|
||||||
|
|
||||||
self._started_at = time.time()
|
self._started_at = time.time()
|
||||||
self._filter = None if not config['main']['filter'] else re.compile(config['main']['filter'])
|
|
||||||
self._current_channel = 0
|
self._current_channel = 0
|
||||||
self._tot_aps = 0
|
self._tot_aps = 0
|
||||||
self._aps_on_channel = 0
|
self._aps_on_channel = 0
|
||||||
@ -164,11 +166,6 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
|||||||
|
|
||||||
self.wait_for(recon_time, sleeping=False)
|
self.wait_for(recon_time, sleeping=False)
|
||||||
|
|
||||||
def _filter_included(self, ap):
|
|
||||||
return self._filter is None or \
|
|
||||||
self._filter.match(ap['hostname']) is not None or \
|
|
||||||
self._filter.match(ap['mac']) is not None
|
|
||||||
|
|
||||||
def set_access_points(self, aps):
|
def set_access_points(self, aps):
|
||||||
self._access_points = aps
|
self._access_points = aps
|
||||||
plugins.on('wifi_update', self, aps)
|
plugins.on('wifi_update', self, aps)
|
||||||
@ -184,13 +181,10 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
|||||||
for ap in s['wifi']['aps']:
|
for ap in s['wifi']['aps']:
|
||||||
if ap['encryption'] == '' or ap['encryption'] == 'OPEN':
|
if ap['encryption'] == '' or ap['encryption'] == 'OPEN':
|
||||||
continue
|
continue
|
||||||
elif ap['hostname'] in whitelist or ap['mac'][:8].lower() in whitelist:
|
elif ap['hostname'] in whitelist or ap['mac'][:13].lower() in whitelist or ap['mac'].lower() in whitelist:
|
||||||
continue
|
continue
|
||||||
elif ap['hostname'] not in whitelist \
|
else:
|
||||||
and ap['mac'].lower() not in whitelist \
|
aps.append(ap)
|
||||||
and ap['mac'][:8].lower() not in whitelist:
|
|
||||||
if self._filter_included(ap):
|
|
||||||
aps.append(ap)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("Error while getting access points (%s)", e)
|
logging.exception("Error while getting access points (%s)", e)
|
||||||
|
|
||||||
@ -262,7 +256,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)
|
||||||
|
|
||||||
@ -277,6 +271,10 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
|||||||
self._save_recovery_data()
|
self._save_recovery_data()
|
||||||
pwnagotchi.reboot()
|
pwnagotchi.reboot()
|
||||||
|
|
||||||
|
def _restart(self, mode='AUTO'):
|
||||||
|
self._save_recovery_data()
|
||||||
|
pwnagotchi.restart(mode)
|
||||||
|
|
||||||
def _save_recovery_data(self):
|
def _save_recovery_data(self):
|
||||||
logging.warning("writing recovery data to %s ...", RECOVERY_DATA_FILE)
|
logging.warning("writing recovery data to %s ...", RECOVERY_DATA_FILE)
|
||||||
with open(RECOVERY_DATA_FILE, 'w') as fp:
|
with open(RECOVERY_DATA_FILE, 'w') as fp:
|
||||||
@ -308,7 +306,8 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def start_session_fetcher(self):
|
def start_session_fetcher(self):
|
||||||
_thread.start_new_thread(self._fetch_stats, ())
|
#_thread.start_new_thread(self._fetch_stats, ())
|
||||||
|
threading.Thread(target=self._fetch_stats, args=(), name="Session Fetcher", daemon=True).start()
|
||||||
|
|
||||||
def _fetch_stats(self):
|
def _fetch_stats(self):
|
||||||
while True:
|
while True:
|
||||||
@ -391,7 +390,8 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
|||||||
|
|
||||||
def start_event_polling(self):
|
def start_event_polling(self):
|
||||||
# start a thread and pass in the mainloop
|
# start a thread and pass in the mainloop
|
||||||
_thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),))
|
#_thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),))
|
||||||
|
threading.Thread(target=self._event_poller, args=(asyncio.get_event_loop(),), name="Event Polling", daemon=True).start()
|
||||||
|
|
||||||
def is_module_running(self, module):
|
def is_module_running(self, module):
|
||||||
s = self.session()
|
s = self.session()
|
||||||
@ -429,7 +429,6 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
|||||||
if self.is_stale():
|
if self.is_stale():
|
||||||
logging.debug("recon is stale, skipping assoc(%s)", ap['mac'])
|
logging.debug("recon is stale, skipping assoc(%s)", ap['mac'])
|
||||||
return
|
return
|
||||||
|
|
||||||
if throttle == -1 and "throttle_a" in self._config['personality']:
|
if throttle == -1 and "throttle_a" in self._config['personality']:
|
||||||
throttle = self._config['personality']['throttle_a']
|
throttle = self._config['personality']['throttle_a']
|
||||||
|
|
||||||
|
@ -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))
|
||||||
@ -59,7 +65,8 @@ def load(config, agent, epoch, from_disk=True):
|
|||||||
|
|
||||||
return a2c
|
return a2c
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("[AI] error while starting AI (%s)", e)
|
logging.info("[AI] Error while starting AI")
|
||||||
|
logging.debug("[AI] error while starting AI (%s)", e)
|
||||||
logging.info("[AI] Deleting brain and restarting.")
|
logging.info("[AI] Deleting brain and restarting.")
|
||||||
os.system("rm /root/brain.nn && service pwnagotchi restart")
|
os.system("rm /root/brain.nn && service pwnagotchi restart")
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import gym
|
import gymnasium as gym
|
||||||
from gym import spaces
|
from gymnasium import spaces
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import pwnagotchi.ai.featurizer as featurizer
|
import pwnagotchi.ai.featurizer as featurizer
|
||||||
@ -51,7 +51,7 @@ class Environment(gym.Env):
|
|||||||
'state_v': None
|
'state_v': None
|
||||||
}
|
}
|
||||||
|
|
||||||
self.action_space = spaces.MultiDiscrete([p.space_size() for p in Environment.params if p.trainable])
|
self.action_space = spaces.multi_discrete.MultiDiscrete([p.space_size() for p in Environment.params if p.trainable])
|
||||||
self.observation_space = spaces.Box(low=0, high=1, shape=featurizer.shape, dtype=np.float32)
|
self.observation_space = spaces.Box(low=0, high=1, shape=featurizer.shape, dtype=np.float32)
|
||||||
self.reward_range = reward.range
|
self.reward_range = reward.range
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from gym import spaces
|
from gymnasium import spaces
|
||||||
|
|
||||||
|
|
||||||
class Parameter(object):
|
class Parameter(object):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import _thread
|
# import _thread
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
@ -111,7 +111,8 @@ class AsyncTrainer(object):
|
|||||||
return self._training_epochs
|
return self._training_epochs
|
||||||
|
|
||||||
def start_ai(self):
|
def start_ai(self):
|
||||||
_thread.start_new_thread(self._ai_worker, ())
|
#_thread.start_new_thread(self._ai_worker, ())
|
||||||
|
threading.Thread(target=self._ai_worker, args=(), name="AI Worker", daemon=True).start()
|
||||||
|
|
||||||
def _save_ai(self):
|
def _save_ai(self):
|
||||||
logging.info("[AI] saving model to %s ..." % self._nn_path)
|
logging.info("[AI] saving model to %s ..." % self._nn_path)
|
||||||
|
@ -138,6 +138,6 @@ class Automata(object):
|
|||||||
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
||||||
|
|
||||||
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
||||||
logging.critical("%d epochs without visible access points -> rebooting ...", self._epoch.blind_for)
|
logging.critical("%d epochs without visible access points -> restarting ...", self._epoch.blind_for)
|
||||||
self._reboot()
|
self._restart()
|
||||||
self._epoch.blind_for = 0
|
self._epoch.blind_for = 0
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
main.name = "pwnagotchi"
|
main.name = "pwnagotchi"
|
||||||
main.lang = "en"
|
main.lang = "en"
|
||||||
|
main.whitelist = [
|
||||||
|
"EXAMPLE_NETWORK",
|
||||||
|
"ANOTHER_EXAMPLE_NETWORK",
|
||||||
|
"fo:od:ba:be:fo:od",
|
||||||
|
"fo:od:ba"
|
||||||
|
]
|
||||||
main.confd = "/etc/pwnagotchi/conf.d/"
|
main.confd = "/etc/pwnagotchi/conf.d/"
|
||||||
main.custom_plugin_repos = [
|
main.custom_plugin_repos = [
|
||||||
"https://github.com/jayofelony/pwnagotchi-torch-plugins/archive/master.zip",
|
"https://github.com/jayofelony/pwnagotchi-torch-plugins/archive/master.zip",
|
||||||
@ -26,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
|
||||||
@ -37,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
|
||||||
@ -55,9 +61,6 @@ main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947"
|
|||||||
|
|
||||||
main.plugins.grid.enabled = true
|
main.plugins.grid.enabled = true
|
||||||
main.plugins.grid.report = true
|
main.plugins.grid.report = true
|
||||||
main.plugins.grid.exclude = [
|
|
||||||
"YourHomeNetworkHere"
|
|
||||||
]
|
|
||||||
|
|
||||||
main.plugins.logtail.enabled = false
|
main.plugins.logtail.enabled = false
|
||||||
main.plugins.logtail.max-lines = 10000
|
main.plugins.logtail.max-lines = 10000
|
||||||
@ -73,14 +76,9 @@ main.plugins.onlinehashcrack.enabled = false
|
|||||||
main.plugins.onlinehashcrack.email = ""
|
main.plugins.onlinehashcrack.email = ""
|
||||||
main.plugins.onlinehashcrack.dashboard = ""
|
main.plugins.onlinehashcrack.dashboard = ""
|
||||||
main.plugins.onlinehashcrack.single_files = false
|
main.plugins.onlinehashcrack.single_files = false
|
||||||
main.plugins.onlinehashcrack.whitelist = []
|
|
||||||
|
|
||||||
main.plugins.paw-gps.enabled = false
|
main.plugins.pisugar3.enabled = false
|
||||||
main.plugins.paw-gps.ip = "192.168.44.1:8080"
|
main.plugins.pisugar3.shutdown = 5
|
||||||
|
|
||||||
main.plugins.pisugar2.enabled = false
|
|
||||||
main.plugins.pisugar2.shutdown = 5
|
|
||||||
main.plugins.pisugar2.sync_rtc_on_boot = false
|
|
||||||
|
|
||||||
main.plugins.session-stats.enabled = true
|
main.plugins.session-stats.enabled = true
|
||||||
main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/"
|
main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/"
|
||||||
@ -100,29 +98,21 @@ main.plugins.webgpsmap.enabled = false
|
|||||||
|
|
||||||
main.plugins.wigle.enabled = false
|
main.plugins.wigle.enabled = false
|
||||||
main.plugins.wigle.api_key = ""
|
main.plugins.wigle.api_key = ""
|
||||||
main.plugins.wigle.whitelist = []
|
main.plugins.wigle.donate = false
|
||||||
main.plugins.wigle.donate = true
|
|
||||||
|
|
||||||
main.plugins.wpa-sec.enabled = false
|
main.plugins.wpa-sec.enabled = false
|
||||||
main.plugins.wpa-sec.api_key = ""
|
main.plugins.wpa-sec.api_key = ""
|
||||||
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
|
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
|
||||||
main.plugins.wpa-sec.download_results = false
|
main.plugins.wpa-sec.download_results = false
|
||||||
main.plugins.wpa-sec.whitelist = []
|
|
||||||
|
|
||||||
main.iface = "wlan0mon"
|
main.iface = "wlan0mon"
|
||||||
main.mon_start_cmd = "/usr/bin/monstart"
|
main.mon_start_cmd = "/usr/bin/monstart"
|
||||||
main.mon_stop_cmd = "/usr/bin/monstop"
|
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.whitelist = [
|
|
||||||
"EXAMPLE_NETWORK",
|
|
||||||
"ANOTHER_EXAMPLE_NETWORK",
|
|
||||||
"fo:od:ba:be:fo:od",
|
|
||||||
"fo:od:ba"
|
|
||||||
]
|
|
||||||
main.filter = ""
|
|
||||||
|
|
||||||
main.log.path = "/home/pi/logs/pwnagotchi.log"
|
main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log"
|
||||||
|
main.log.path-debug = "/etc/pwnagotchi/log/pwnagotchi-debug.log"
|
||||||
main.log.rotation.enabled = true
|
main.log.rotation.enabled = true
|
||||||
main.log.rotation.size = "10M"
|
main.log.rotation.size = "10M"
|
||||||
|
|
||||||
@ -160,6 +150,8 @@ personality.bond_encounters_factor = 20000
|
|||||||
personality.throttle_a = 0.4
|
personality.throttle_a = 0.4
|
||||||
personality.throttle_d = 0.9
|
personality.throttle_d = 0.9
|
||||||
|
|
||||||
|
ui.invert = false # false = black background, true = white background
|
||||||
|
ui.cursor = true
|
||||||
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
|
||||||
@ -189,6 +181,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
|
||||||
@ -202,11 +197,6 @@ ui.display.enabled = false
|
|||||||
ui.display.rotation = 180
|
ui.display.rotation = 180
|
||||||
ui.display.type = "waveshare_4"
|
ui.display.type = "waveshare_4"
|
||||||
|
|
||||||
bettercap.scheme = "http"
|
|
||||||
bettercap.hostname = "localhost"
|
|
||||||
bettercap.port = 8081
|
|
||||||
bettercap.username = "pwnagotchi"
|
|
||||||
bettercap.password = "pwnagotchi"
|
|
||||||
bettercap.handshakes = "/root/handshakes"
|
bettercap.handshakes = "/root/handshakes"
|
||||||
bettercap.silence = [
|
bettercap.silence = [
|
||||||
"ble.device.new",
|
"ble.device.new",
|
||||||
@ -225,7 +215,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
|
||||||
|
@ -3,7 +3,8 @@ import re
|
|||||||
import tempfile
|
import tempfile
|
||||||
import contextlib
|
import contextlib
|
||||||
import shutil
|
import shutil
|
||||||
import _thread
|
#import _thread
|
||||||
|
import threading
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
@ -85,7 +86,8 @@ def setup_mounts(config):
|
|||||||
if interval:
|
if interval:
|
||||||
logging.debug("[FS] Starting thread to sync %s (interval: %d)",
|
logging.debug("[FS] Starting thread to sync %s (interval: %d)",
|
||||||
options['mount'], interval)
|
options['mount'], interval)
|
||||||
_thread.start_new_thread(m.daemonize, (interval,))
|
threading.Thread(target=m.daemonize, args=(interval,),name="File Sys", daemon=True).start()
|
||||||
|
#_thread.start_new_thread(m.daemonize, (interval,))
|
||||||
else:
|
else:
|
||||||
logging.debug("[FS] Not syncing %s, because interval is 0",
|
logging.debug("[FS] Not syncing %s, because interval is 0",
|
||||||
options['mount'])
|
options['mount'])
|
||||||
|
@ -89,10 +89,10 @@ 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"),
|
||||||
'opwngrid': subprocess.getoutput("pwngrid -version")
|
'opwngrid': subprocess.getoutput("pwngrid -version")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binary file not shown.
@ -1,14 +1,13 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
# Pwnagotchi display English to Esperanto.
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR MADE THIS IN YEAR 2024.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
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: 2023-11-16 21:10+0100\n"
|
"POT-Creation-Date: 2024-01-25 23:40+0100\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"
|
||||||
@ -18,218 +17,219 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "ZzzzZZzzzzZzzz"
|
msgid "ZzzzZZzzzzZzzz"
|
||||||
msgstr ""
|
msgstr "ZzzzZZzzzzZzzz"
|
||||||
|
|
||||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||||
msgstr ""
|
msgstr "Sal, mi estas Pwnagotchi! Komencante…"
|
||||||
|
|
||||||
msgid "New day, new hunt, new pwns!"
|
msgid "New day, new hunt, new pwns!"
|
||||||
msgstr ""
|
msgstr "Nova tago, nova ĉaso, nova wifi!"
|
||||||
|
|
||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr "Eniru la elektronikon!"
|
||||||
|
|
||||||
msgid "AI ready."
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr "Mi pretas."
|
||||||
|
|
||||||
msgid "The neural network is ready."
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr "La elektronika reto estas preta."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr ""
|
msgstr "Mi generas ŝlosilojn, ne malŝaltu min!"
|
||||||
|
|
||||||
#, 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 ""
|
msgstr "Hej, kanalo {channel} disponeblas! Via alirpunkto dankos vin"
|
||||||
|
|
||||||
msgid "Reading last session logs ..."
|
msgid "Reading last session logs ..."
|
||||||
msgstr ""
|
msgstr "Legante protokolojn de antaŭa sesio…"
|
||||||
|
|
||||||
#, 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 "legi {lines_so_far} liniojn ĝis nun…"
|
||||||
|
|
||||||
msgid "I'm bored ..."
|
msgid "I'm bored ..."
|
||||||
msgstr ""
|
msgstr "Mi enuas…"
|
||||||
|
|
||||||
msgid "Let's go for a walk!"
|
msgid "Let's go for a walk!"
|
||||||
msgstr ""
|
msgstr "Ni iru promeni!"
|
||||||
|
|
||||||
msgid "This is the best day of my life!"
|
msgid "This is the best day of my life!"
|
||||||
msgstr ""
|
msgstr "Plej bona tago de mia vivo!"
|
||||||
|
|
||||||
msgid "Shitty day :/"
|
msgid "Shitty day :/"
|
||||||
msgstr ""
|
msgstr "Terura tago :/"
|
||||||
|
|
||||||
msgid "I'm extremely bored ..."
|
msgid "I'm extremely bored ..."
|
||||||
msgstr ""
|
msgstr "mi estas tre enuigita…"
|
||||||
|
|
||||||
msgid "I'm very sad ..."
|
msgid "I'm very sad ..."
|
||||||
msgstr ""
|
msgstr "Mi estas tre malĝoja…"
|
||||||
|
|
||||||
msgid "I'm sad"
|
msgid "I'm sad"
|
||||||
msgstr ""
|
msgstr "Mi estas malfeliĉa"
|
||||||
|
|
||||||
msgid "Leave me alone ..."
|
msgid "Leave me alone ..."
|
||||||
msgstr ""
|
msgstr "Lasu min sola…"
|
||||||
|
|
||||||
msgid "I'm mad at you!"
|
msgid "I'm mad at you!"
|
||||||
msgstr ""
|
msgstr "Mi koleras kontraŭ vi!"
|
||||||
|
|
||||||
msgid "I'm living the life!"
|
msgid "I'm living the life!"
|
||||||
msgstr ""
|
msgstr "Mi ĝuas la vivon!"
|
||||||
|
|
||||||
msgid "I pwn therefore I am."
|
msgid "I pwn therefore I am."
|
||||||
msgstr ""
|
msgstr "Mi eniras tial mi estas."
|
||||||
|
|
||||||
msgid "So many networks!!!"
|
msgid "So many networks!!!"
|
||||||
msgstr ""
|
msgstr "Tiom da ludiloj!"
|
||||||
|
|
||||||
msgid "I'm having so much fun!"
|
msgid "I'm having so much fun!"
|
||||||
msgstr ""
|
msgstr "Mi tre amuzas!"
|
||||||
|
|
||||||
msgid "My crime is that of curiosity ..."
|
msgid "My crime is that of curiosity ..."
|
||||||
msgstr ""
|
msgstr "Scivolemo estas mia krimo…"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hello {name}! Nice to meet you."
|
msgid "Hello {name}! Nice to meet you."
|
||||||
msgstr ""
|
msgstr "Sal {name}! Mi ĝojas renkonti vin."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Yo {name}! Sup?"
|
msgid "Yo {name}! Sup?"
|
||||||
msgstr ""
|
msgstr "Hej {name}! Sal?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey {name} how are you doing?"
|
msgid "Hey {name} how are you doing?"
|
||||||
msgstr ""
|
msgstr "Sal {name}! Kiel vi fartas?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Unit {name} is nearby!"
|
msgid "Unit {name} is nearby!"
|
||||||
msgstr ""
|
msgstr "Iu estas proksime! Ĝia nomo estas {name}."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uhm ... goodbye {name}"
|
msgid "Uhm ... goodbye {name}"
|
||||||
msgstr ""
|
msgstr "Adiaŭ {name}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} is gone ..."
|
msgid "{name} is gone ..."
|
||||||
msgstr ""
|
msgstr "{name} malaperis…"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Whoops ... {name} is gone."
|
msgid "Whoops ... {name} is gone."
|
||||||
msgstr ""
|
msgstr "Hups… {name} malaperis…"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} missed!"
|
msgid "{name} missed!"
|
||||||
msgstr ""
|
msgstr "{name} mankis!"
|
||||||
|
|
||||||
msgid "Missed!"
|
msgid "Missed!"
|
||||||
msgstr ""
|
msgstr "Maltrafis!"
|
||||||
|
|
||||||
msgid "Good friends are a blessing!"
|
msgid "Good friends are a blessing!"
|
||||||
msgstr ""
|
msgstr "Bonaj amikoj estas beno!"
|
||||||
|
|
||||||
msgid "I love my friends!"
|
msgid "I love my friends!"
|
||||||
msgstr ""
|
msgstr "Mi amas miajn amikojn!"
|
||||||
|
|
||||||
msgid "Nobody wants to play with me ..."
|
msgid "Nobody wants to play with me ..."
|
||||||
msgstr ""
|
msgstr "Neniu volas ludi kun mi..."
|
||||||
|
|
||||||
msgid "I feel so alone ..."
|
msgid "I feel so alone ..."
|
||||||
msgstr ""
|
msgstr "Mi estas tiel sola..."
|
||||||
|
|
||||||
msgid "Where's everybody?!"
|
msgid "Where's everybody?!"
|
||||||
msgstr ""
|
msgstr "KIE ĈIUJ ESTAS?!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Napping for {secs}s ..."
|
msgid "Napping for {secs}s ..."
|
||||||
msgstr ""
|
msgstr "Dormeto por {sec}j…"
|
||||||
|
|
||||||
msgid "Zzzzz"
|
msgid "Zzzzz"
|
||||||
msgstr ""
|
msgstr "Zzzzz"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "ZzzZzzz ({secs}s)"
|
msgid "ZzzZzzz ({secs}s)"
|
||||||
msgstr ""
|
msgstr "ZzzZzzz ({sec}j)"
|
||||||
|
|
||||||
msgid "Good night."
|
msgid "Good night."
|
||||||
msgstr ""
|
msgstr "Bonan nokton"
|
||||||
|
|
||||||
msgid "Zzz"
|
msgid "Zzz"
|
||||||
msgstr ""
|
msgstr "Zzz"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Waiting for {secs}s ..."
|
msgid "Waiting for {secs}s ..."
|
||||||
msgstr ""
|
msgstr "Atendas {sec}j…"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Looking around ({secs}s)"
|
msgid "Looking around ({secs}s)"
|
||||||
msgstr ""
|
msgstr "Ĉirkaŭrigardante ({sec}j)"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey {what} let's be friends!"
|
msgid "Hey {what} let's be friends!"
|
||||||
msgstr ""
|
msgstr "Hej {what}, ni estu amikoj!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Associating to {what}"
|
msgid "Associating to {what}"
|
||||||
msgstr ""
|
msgstr "asociante al {what}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Yo {what}!"
|
msgid "Yo {what}!"
|
||||||
msgstr ""
|
msgstr "Hej {what}!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Just decided that {mac} needs no WiFi!"
|
msgid "Just decided that {mac} needs no WiFi!"
|
||||||
msgstr ""
|
msgstr "Ĵus decidis, ke {mac} ne bezonas konekton!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Deauthenticating {mac}"
|
msgid "Deauthenticating {mac}"
|
||||||
msgstr ""
|
msgstr "Malaŭtentigi {mac}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Kickbanning {mac}!"
|
msgid "Kickbanning {mac}!"
|
||||||
msgstr ""
|
msgstr "Forigante {mac}!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Cool, we got {num} new handshake{plural}!"
|
msgid "Cool, we got {num} new handshake{plural}!"
|
||||||
msgstr ""
|
msgstr "Mirinda, ni havas {num} novajn manpremojn!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "You have {count} new message{plural}!"
|
msgid "You have {count} new message{plural}!"
|
||||||
msgstr ""
|
msgstr "Vi nun havas {num} novajn mesaĝojn"
|
||||||
|
|
||||||
msgid "Oops, something went wrong ... Rebooting ..."
|
msgid "Oops, something went wrong ... Rebooting ..."
|
||||||
msgstr ""
|
msgstr "Lo okazis... Rekomencante…"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uploading data to {to} ..."
|
msgid "Uploading data to {to} ..."
|
||||||
msgstr ""
|
msgstr "alŝuti datumojn al {to}…"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Downloading from {name} ..."
|
msgid "Downloading from {name} ..."
|
||||||
msgstr ""
|
msgstr "Elŝutu de {name}…"
|
||||||
|
|
||||||
#, python-brace-format
|
#, fuzzy, python-brace-format
|
||||||
msgid "Kicked {num} stations\n"
|
msgid "Kicked {num} stations\n"
|
||||||
msgstr ""
|
msgstr "Forigita de {num} stacioj"
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
msgid "Made >999 new friends\n"
|
msgid "Made >999 new friends\n"
|
||||||
msgstr ""
|
msgstr "Faris pli ol 999 novajn amikojn!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, fuzzy, python-brace-format
|
||||||
msgid "Made {num} new friends\n"
|
msgid "Made {num} new friends\n"
|
||||||
msgstr ""
|
msgstr "faris (nombro) novajn amikojn"
|
||||||
|
|
||||||
#, python-brace-format
|
#, fuzzy, python-brace-format
|
||||||
msgid "Got {num} handshakes\n"
|
msgid "Got {num} handshakes\n"
|
||||||
msgstr ""
|
msgstr "Ricevis {num} novajn manpremojn"
|
||||||
|
|
||||||
msgid "Met 1 peer"
|
msgid "Met 1 peer"
|
||||||
msgstr ""
|
msgstr "Renkontita unu kolego"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Met {num} peers"
|
msgid "Met {num} peers"
|
||||||
msgstr ""
|
msgstr "renkontitajn {num} kolegojn"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -237,21 +237,24 @@ msgid ""
|
|||||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Mi pwning dum {duration} kaj piedbatis {deauthed} klientojn!Mi ankaŭ "
|
||||||
|
"renkontis {associated} novajn amikojn kaj manĝis {handshakes} manpremojn!"
|
||||||
|
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
|
|
||||||
msgid "hours"
|
msgid "hours"
|
||||||
msgstr ""
|
msgstr "horror"
|
||||||
|
|
||||||
msgid "minutes"
|
msgid "minutes"
|
||||||
msgstr ""
|
msgstr "minutoj"
|
||||||
|
|
||||||
msgid "seconds"
|
msgid "seconds"
|
||||||
msgstr ""
|
msgstr "sekundoj"
|
||||||
|
|
||||||
msgid "hour"
|
msgid "hour"
|
||||||
msgstr ""
|
msgstr "horo"
|
||||||
|
|
||||||
msgid "minute"
|
msgid "minute"
|
||||||
msgstr ""
|
msgstr "minuto"
|
||||||
|
|
||||||
msgid "second"
|
msgid "second"
|
||||||
msgstr ""
|
msgstr "dua"
|
||||||
|
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"
|
||||||
|
Binary file not shown.
@ -1,16 +1,16 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
# Icelandic Translation.
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
# Copyright (C) 2019
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the pwnagotchi package..
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# Sean Duggan <sean.duggan@pm.me>, 2024.
|
||||||
#
|
#
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
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: 2023-11-16 21:10+0100\n"
|
"POT-Creation-Date: 2024-11-17 20:51+0100\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 <sean.duggan@pm.me>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"Language: Icelandic\n"
|
"Language: Icelandic\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@ -18,218 +18,219 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "ZzzzZZzzzzZzzz"
|
msgid "ZzzzZZzzzzZzzz"
|
||||||
msgstr ""
|
msgstr "ZzzzZZzzzzZzzz"
|
||||||
|
|
||||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||||
msgstr ""
|
msgstr "Hæ, Ég heiti Pwnagotchi! Ræsi.."
|
||||||
|
|
||||||
msgid "New day, new hunt, new pwns!"
|
msgid "New day, new hunt, new pwns!"
|
||||||
msgstr ""
|
msgstr "Nýr dagur, Ný veiði, Ný pwns!"
|
||||||
|
|
||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr "Hakkaðu plánetuna!"
|
||||||
|
|
||||||
msgid "AI ready."
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr "AI tilbúið."
|
||||||
|
|
||||||
msgid "The neural network is ready."
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr "Tauganetið er tilbúið."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr ""
|
msgstr "Bý til lykla, ekki slökkva á."
|
||||||
|
|
||||||
#, 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 ""
|
msgstr "Hæ, Rás {channel} er líka ókeypis! AP þinn mun þakka fyrir."
|
||||||
|
|
||||||
msgid "Reading last session logs ..."
|
msgid "Reading last session logs ..."
|
||||||
msgstr ""
|
msgstr "Les fyrri leiðarbók..."
|
||||||
|
|
||||||
#, 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 "Ég las {lines_so_far} leiðarbók línur hingað til..."
|
||||||
|
|
||||||
msgid "I'm bored ..."
|
msgid "I'm bored ..."
|
||||||
msgstr ""
|
msgstr "Mér leiðist ..."
|
||||||
|
|
||||||
msgid "Let's go for a walk!"
|
msgid "Let's go for a walk!"
|
||||||
msgstr ""
|
msgstr "Förum í göngutúr!"
|
||||||
|
|
||||||
msgid "This is the best day of my life!"
|
msgid "This is the best day of my life!"
|
||||||
msgstr ""
|
msgstr "Þetta er besti dagur lífs míns!"
|
||||||
|
|
||||||
msgid "Shitty day :/"
|
msgid "Shitty day :/"
|
||||||
msgstr ""
|
msgstr "Skítadagur :/"
|
||||||
|
|
||||||
msgid "I'm extremely bored ..."
|
msgid "I'm extremely bored ..."
|
||||||
msgstr ""
|
msgstr "Mér leiðist óskaplega mikið ..."
|
||||||
|
|
||||||
msgid "I'm very sad ..."
|
msgid "I'm very sad ..."
|
||||||
msgstr ""
|
msgstr "Ég er mjög leiður ..."
|
||||||
|
|
||||||
msgid "I'm sad"
|
msgid "I'm sad"
|
||||||
msgstr ""
|
msgstr "Ég er leiður"
|
||||||
|
|
||||||
msgid "Leave me alone ..."
|
msgid "Leave me alone ..."
|
||||||
msgstr ""
|
msgstr "Láttu mig í friði ..."
|
||||||
|
|
||||||
msgid "I'm mad at you!"
|
msgid "I'm mad at you!"
|
||||||
msgstr ""
|
msgstr "Ég er reiður út í þig!"
|
||||||
|
|
||||||
msgid "I'm living the life!"
|
msgid "I'm living the life!"
|
||||||
msgstr ""
|
msgstr "Ég lifi besta lífi!"
|
||||||
|
|
||||||
msgid "I pwn therefore I am."
|
msgid "I pwn therefore I am."
|
||||||
msgstr ""
|
msgstr "Ég pwn þess vegna er ég."
|
||||||
|
|
||||||
msgid "So many networks!!!"
|
msgid "So many networks!!!"
|
||||||
msgstr ""
|
msgstr "Svo mörg net!!!"
|
||||||
|
|
||||||
msgid "I'm having so much fun!"
|
msgid "I'm having so much fun!"
|
||||||
msgstr ""
|
msgstr "Mér finnst svo gaman!"
|
||||||
|
|
||||||
msgid "My crime is that of curiosity ..."
|
msgid "My crime is that of curiosity ..."
|
||||||
msgstr ""
|
msgstr "Glæpur minn er forvitni ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hello {name}! Nice to meet you."
|
msgid "Hello {name}! Nice to meet you."
|
||||||
msgstr ""
|
msgstr "Hæ {name}! Gaman að hitta þig!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Yo {name}! Sup?"
|
msgid "Yo {name}! Sup?"
|
||||||
msgstr ""
|
msgstr "Yo {name}! Sup?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey {name} how are you doing?"
|
msgid "Hey {name} how are you doing?"
|
||||||
msgstr ""
|
msgstr "Hæ {name} Hvernig hefurðu það?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Unit {name} is nearby!"
|
msgid "Unit {name} is nearby!"
|
||||||
msgstr ""
|
msgstr "Tæki {name} er nálægt!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uhm ... goodbye {name}"
|
msgid "Uhm ... goodbye {name}"
|
||||||
msgstr ""
|
msgstr "Uhm ... bless {name}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} is gone ..."
|
msgid "{name} is gone ..."
|
||||||
msgstr ""
|
msgstr "{name} er farinn ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Whoops ... {name} is gone."
|
msgid "Whoops ... {name} is gone."
|
||||||
msgstr ""
|
msgstr "Úps ... {name} er farinn."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} missed!"
|
msgid "{name} missed!"
|
||||||
msgstr ""
|
msgstr "{name} saknað!'"
|
||||||
|
|
||||||
msgid "Missed!"
|
msgid "Missed!"
|
||||||
msgstr ""
|
msgstr "Saknað!'"
|
||||||
|
|
||||||
msgid "Good friends are a blessing!"
|
msgid "Good friends are a blessing!"
|
||||||
msgstr ""
|
msgstr "Góðir vinir eru blessun!"
|
||||||
|
|
||||||
msgid "I love my friends!"
|
msgid "I love my friends!"
|
||||||
msgstr ""
|
msgstr "Ég elska vini mína!"
|
||||||
|
|
||||||
msgid "Nobody wants to play with me ..."
|
msgid "Nobody wants to play with me ..."
|
||||||
msgstr ""
|
msgstr "Enginn vill leika við mig ..."
|
||||||
|
|
||||||
msgid "I feel so alone ..."
|
msgid "I feel so alone ..."
|
||||||
msgstr ""
|
msgstr "Mér finnst ég vera svo ein ..."
|
||||||
|
|
||||||
msgid "Where's everybody?!"
|
msgid "Where's everybody?!"
|
||||||
msgstr ""
|
msgstr "Hvar eru allir?!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Napping for {secs}s ..."
|
msgid "Napping for {secs}s ..."
|
||||||
msgstr ""
|
msgstr "Sef í {secs}s ..."
|
||||||
|
|
||||||
msgid "Zzzzz"
|
msgid "Zzzzz"
|
||||||
msgstr ""
|
msgstr "Zzzzz"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "ZzzZzzz ({secs}s)"
|
msgid "ZzzZzzz ({secs}s)"
|
||||||
msgstr ""
|
msgstr "ZzzZzzz ({secs})"
|
||||||
|
|
||||||
msgid "Good night."
|
msgid "Good night."
|
||||||
msgstr ""
|
msgstr "Góða nótt."
|
||||||
|
|
||||||
msgid "Zzz"
|
msgid "Zzz"
|
||||||
msgstr ""
|
msgstr "Zzz"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Waiting for {secs}s ..."
|
msgid "Waiting for {secs}s ..."
|
||||||
msgstr ""
|
msgstr "Bíð eftir {secs} ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Looking around ({secs}s)"
|
msgid "Looking around ({secs}s)"
|
||||||
msgstr ""
|
msgstr "Horfi í kringum mig ({secs}s)"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey {what} let's be friends!"
|
msgid "Hey {what} let's be friends!"
|
||||||
msgstr ""
|
msgstr "Hæ {what} við skulum vera vinir!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Associating to {what}"
|
msgid "Associating to {what}"
|
||||||
msgstr ""
|
msgstr "Tengist við {what}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Yo {what}!"
|
msgid "Yo {what}!"
|
||||||
msgstr ""
|
msgstr "Yo {what}!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Just decided that {mac} needs no WiFi!"
|
msgid "Just decided that {mac} needs no WiFi!"
|
||||||
msgstr ""
|
msgstr "Ég ákvað að {mac} þurfi ekki WiFi!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Deauthenticating {mac}"
|
msgid "Deauthenticating {mac}"
|
||||||
msgstr ""
|
msgstr "Afvotta {mac}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Kickbanning {mac}!"
|
msgid "Kickbanning {mac}!"
|
||||||
msgstr ""
|
msgstr "Sparkbanna {mac}!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Cool, we got {num} new handshake{plural}!"
|
msgid "Cool, we got {num} new handshake{plural}!"
|
||||||
msgstr ""
|
msgstr "Flott! við fengum {num} ný handatök {plural}!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "You have {count} new message{plural}!"
|
msgid "You have {count} new message{plural}!"
|
||||||
msgstr ""
|
msgstr "þú hefur {count} ný skilaboð!"
|
||||||
|
|
||||||
msgid "Oops, something went wrong ... Rebooting ..."
|
msgid "Oops, something went wrong ... Rebooting ..."
|
||||||
msgstr ""
|
msgstr "Oops, eitthvað brotnaði ... Endurræsi ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uploading data to {to} ..."
|
msgid "Uploading data to {to} ..."
|
||||||
msgstr ""
|
msgstr "Hleð upp gögnum til {to} ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, fuzzy, python-brace-format
|
||||||
msgid "Downloading from {name} ..."
|
msgid "Downloading from {name} ..."
|
||||||
msgstr ""
|
msgstr "Hleð upp gögnum til {to} ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Kicked {num} stations\n"
|
msgid "Kicked {num} stations\n"
|
||||||
msgstr ""
|
msgstr "Sparkaði {num} stöðvar\n"
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
msgid "Made >999 new friends\n"
|
msgid "Made >999 new friends\n"
|
||||||
msgstr ""
|
msgstr "Eignaðist {num} nýja vini\n"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Made {num} new friends\n"
|
msgid "Made {num} new friends\n"
|
||||||
msgstr ""
|
msgstr "Eignaðist {num} nýja vini\n"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Got {num} handshakes\n"
|
msgid "Got {num} handshakes\n"
|
||||||
msgstr ""
|
msgstr "Fékk {num} ný handabönd\n"
|
||||||
|
|
||||||
msgid "Met 1 peer"
|
msgid "Met 1 peer"
|
||||||
msgstr ""
|
msgstr "Hitti 1 jafningja"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Met {num} peers"
|
msgid "Met {num} peers"
|
||||||
msgstr ""
|
msgstr "Hitti {num} jafningja"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -237,21 +238,24 @@ msgid ""
|
|||||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Ég hef verið að pwna í {duration} og sparkað {deauthed} viðskiptavinum! Ég "
|
||||||
|
"hef líka hitt {associated} nýja vini og borðað {handshakes} handabönd! "
|
||||||
|
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
|
|
||||||
msgid "hours"
|
msgid "hours"
|
||||||
msgstr ""
|
msgstr "klukkustundir"
|
||||||
|
|
||||||
msgid "minutes"
|
msgid "minutes"
|
||||||
msgstr ""
|
msgstr "mínútur"
|
||||||
|
|
||||||
msgid "seconds"
|
msgid "seconds"
|
||||||
msgstr ""
|
msgstr "sekúndur"
|
||||||
|
|
||||||
msgid "hour"
|
msgid "hour"
|
||||||
msgstr ""
|
msgstr "klukkustund"
|
||||||
|
|
||||||
msgid "minute"
|
msgid "minute"
|
||||||
msgstr ""
|
msgstr "mínútu"
|
||||||
|
|
||||||
msgid "second"
|
msgid "second"
|
||||||
msgstr ""
|
msgstr "sekúndu"
|
||||||
|
Binary file not shown.
@ -16,7 +16,7 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "ZzzzZZzzzzZzzz"
|
msgid "ZzzzZZzzzzZzzz"
|
||||||
msgstr ""
|
msgstr "ZzzzZZzzzzZzzz"
|
||||||
|
|
||||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||||
msgstr "Ciao! Piacere Pwnagotchi! Caricamento ..."
|
msgstr "Ciao! Piacere Pwnagotchi! Caricamento ..."
|
||||||
@ -25,7 +25,7 @@ msgid "New day, new hunt, new pwns!"
|
|||||||
msgstr "Nuovo giorno...nuovi handshakes!!!"
|
msgstr "Nuovo giorno...nuovi handshakes!!!"
|
||||||
|
|
||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr "Hack il Pianeta"
|
||||||
|
|
||||||
msgid "AI ready."
|
msgid "AI ready."
|
||||||
msgstr "IA pronta."
|
msgstr "IA pronta."
|
||||||
@ -34,18 +34,18 @@ msgid "The neural network is ready."
|
|||||||
msgstr "La rete neurale è pronta."
|
msgstr "La rete neurale è pronta."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr ""
|
msgstr "Generazione di chiavi, non spegnere"
|
||||||
|
|
||||||
#, 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 "Hey, il canale {channel} è libero! Il tuo AP ringrazia."
|
msgstr "Hey, il canale {channel} è libero! Il tuo AP ringrazia."
|
||||||
|
|
||||||
msgid "Reading last session logs ..."
|
msgid "Reading last session logs ..."
|
||||||
msgstr ""
|
msgstr "Lettura dei log dell'ultima sessione ..."
|
||||||
|
|
||||||
#, 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 "Leggi le righe di log {lines_so_far} finora ..."
|
||||||
|
|
||||||
msgid "I'm bored ..."
|
msgid "I'm bored ..."
|
||||||
msgstr "Che noia ..."
|
msgstr "Che noia ..."
|
||||||
@ -54,7 +54,7 @@ msgid "Let's go for a walk!"
|
|||||||
msgstr "Andiamo a fare una passeggiata!"
|
msgstr "Andiamo a fare una passeggiata!"
|
||||||
|
|
||||||
msgid "This is the best day of my life!"
|
msgid "This is the best day of my life!"
|
||||||
msgstr "Questo è il più bel giorno della mia vita!!!!"
|
msgstr "Questo e il miglior giorno della mia vita!!!!"
|
||||||
|
|
||||||
msgid "Shitty day :/"
|
msgid "Shitty day :/"
|
||||||
msgstr "Giorno di merda :/"
|
msgstr "Giorno di merda :/"
|
||||||
@ -72,22 +72,22 @@ msgid "Leave me alone ..."
|
|||||||
msgstr "Mi sento così solo..."
|
msgstr "Mi sento così solo..."
|
||||||
|
|
||||||
msgid "I'm mad at you!"
|
msgid "I'm mad at you!"
|
||||||
msgstr ""
|
msgstr "sono arabiata con te"
|
||||||
|
|
||||||
msgid "I'm living the life!"
|
msgid "I'm living the life!"
|
||||||
msgstr "Mi sento vivo!"
|
msgstr "sono viva la vita!"
|
||||||
|
|
||||||
msgid "I pwn therefore I am."
|
msgid "I pwn therefore I am."
|
||||||
msgstr "Pwn ergo sum."
|
msgstr "Pwn ergo sum."
|
||||||
|
|
||||||
msgid "So many networks!!!"
|
msgid "So many networks!!!"
|
||||||
msgstr "Qui è pieno di reti!"
|
msgstr "Qui pieno di reti!"
|
||||||
|
|
||||||
msgid "I'm having so much fun!"
|
msgid "I'm having so much fun!"
|
||||||
msgstr "Mi sto divertendo tantissimo!"
|
msgstr "Mi sto divertendo tantissimo!"
|
||||||
|
|
||||||
msgid "My crime is that of curiosity ..."
|
msgid "My crime is that of curiosity ..."
|
||||||
msgstr ""
|
msgstr "Il mio crimine ? quello della curiosit?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hello {name}! Nice to meet you."
|
msgid "Hello {name}! Nice to meet you."
|
||||||
@ -95,15 +95,15 @@ msgstr "Ciao {name}! E' un piacere."
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Yo {name}! Sup?"
|
msgid "Yo {name}! Sup?"
|
||||||
msgstr ""
|
msgstr "Yo {name} Come va"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey {name} how are you doing?"
|
msgid "Hey {name} how are you doing?"
|
||||||
msgstr ""
|
msgstr "Ehi {name} come stai?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Unit {name} is nearby!"
|
msgid "Unit {name} is nearby!"
|
||||||
msgstr "L'Unità {name} è vicina!"
|
msgstr "L'Unit {name} e vicina!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uhm ... goodbye {name}"
|
msgid "Uhm ... goodbye {name}"
|
||||||
@ -111,30 +111,30 @@ msgstr "Uhm ... addio {name}, mi mancherai..."
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} is gone ..."
|
msgid "{name} is gone ..."
|
||||||
msgstr "{name} se n'è andato ..."
|
msgstr "{name} se andato ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Whoops ... {name} is gone."
|
msgid "Whoops ... {name} is gone."
|
||||||
msgstr "Whoops ...{name} se n'è andato."
|
msgstr "Whoops ...{name} se andato."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} missed!"
|
msgid "{name} missed!"
|
||||||
msgstr "{name} è scomparso..."
|
msgstr "{name} scomparso..."
|
||||||
|
|
||||||
msgid "Missed!"
|
msgid "Missed!"
|
||||||
msgstr "Ehi! Dove sei andato!?"
|
msgstr "Ehi! Dove sei andato!?"
|
||||||
|
|
||||||
msgid "Good friends are a blessing!"
|
msgid "Good friends are a blessing!"
|
||||||
msgstr ""
|
msgstr "Buoni amici sono una benedizione"
|
||||||
|
|
||||||
msgid "I love my friends!"
|
msgid "I love my friends!"
|
||||||
msgstr ""
|
msgstr "Amo i miei amici"
|
||||||
|
|
||||||
msgid "Nobody wants to play with me ..."
|
msgid "Nobody wants to play with me ..."
|
||||||
msgstr "Nessuno vuole giocare con me..."
|
msgstr "Nessuno vuole giocare con me..."
|
||||||
|
|
||||||
msgid "I feel so alone ..."
|
msgid "I feel so alone ..."
|
||||||
msgstr "Mi sento così solo..."
|
msgstr "Mi sento cos solo..."
|
||||||
|
|
||||||
msgid "Where's everybody?!"
|
msgid "Where's everybody?!"
|
||||||
msgstr "Dove sono tutti?!"
|
msgstr "Dove sono tutti?!"
|
||||||
@ -144,17 +144,17 @@ msgid "Napping for {secs}s ..."
|
|||||||
msgstr "Schiaccio un pisolino per {secs}s ..."
|
msgstr "Schiaccio un pisolino per {secs}s ..."
|
||||||
|
|
||||||
msgid "Zzzzz"
|
msgid "Zzzzz"
|
||||||
msgstr ""
|
msgstr "Zzzzz"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "ZzzZzzz ({secs}s)"
|
msgid "ZzzZzzz ({secs}s)"
|
||||||
msgstr ""
|
msgstr "ZzzZzzz ({secs}s)"
|
||||||
|
|
||||||
msgid "Good night."
|
msgid "Good night."
|
||||||
msgstr ""
|
msgstr "Buona notte"
|
||||||
|
|
||||||
msgid "Zzz"
|
msgid "Zzz"
|
||||||
msgstr ""
|
msgstr "Zzz"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Waiting for {secs}s ..."
|
msgid "Waiting for {secs}s ..."
|
||||||
@ -182,7 +182,7 @@ msgstr "Ho appena deciso che {mac} non necessita di WiFi!"
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Deauthenticating {mac}"
|
msgid "Deauthenticating {mac}"
|
||||||
msgstr ""
|
msgstr "Annullamento dell'autenticazione {mac}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Kickbanning {mac}!"
|
msgid "Kickbanning {mac}!"
|
||||||
@ -201,11 +201,11 @@ msgstr "Ops, qualcosa è andato storto ... Riavvio ..."
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uploading data to {to} ..."
|
msgid "Uploading data to {to} ..."
|
||||||
msgstr ""
|
msgstr "Caricamento dei dati in {to}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Downloading from {name} ..."
|
msgid "Downloading from {name} ..."
|
||||||
msgstr ""
|
msgstr "Scaricamento da {name} ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Kicked {num} stations\n"
|
msgid "Kicked {num} stations\n"
|
||||||
|
Binary file not shown.
@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: 0.0.1\n"
|
"Project-Id-Version: 0.0.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||||
"PO-Revision-Date: 2019-10-16 15:05+0200\n"
|
"PO-Revision-Date: 2024-08-23 20:40+0900\n"
|
||||||
"Last-Translator: wytshadow <24534649+wytshadow@users.noreply.github.com>\n"
|
"Last-Translator: mendoitarou_ <42207304+mendoitarou@users.noreply.github.com>\n"
|
||||||
"Language-Team: pwnagotchi <24534649+wytshadow@users.noreply.github.com>\n"
|
"Language-Team: pwnagotchi <24534649+wytshadow@users.noreply.github.com>\n"
|
||||||
"Language: Japanese\n"
|
"Language: Japanese\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@ -243,10 +243,12 @@ msgstr ""
|
|||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uploading data to {to} ..."
|
msgid "Uploading data to {to} ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"{to} にデータをアップロードしてるよ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Downloading from {name} ..."
|
msgid "Downloading from {name} ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"{name} からダウンロードしてるよ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Kicked {num} stations\n"
|
msgid "Kicked {num} stations\n"
|
||||||
|
Binary file not shown.
@ -1,33 +1,33 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
# pwnagotchi Brazilian Portuguese translation file.
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
# Copyright (C) 2024
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the pwnagotchi package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# Fabiano F O <fabfernandes@hotmail.com>, 2024.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
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: 2023-11-17 15:46+0100\n"
|
"POT-Creation-Date: 2024-03-25 22:30+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: Foxy <EMAIL@ADDRESS>\n"
|
"Last-Translator: Fabiano F O <fabfernandes@hotmail.com>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"Language: Portuguese (Brazil)\n"
|
"Language: Brazilian Portuguese\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=CHARSET\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "ZzzzZZzzzzZzzz"
|
msgid "ZzzzZZzzzzZzzz"
|
||||||
msgstr "ZzzzZZzzzzZzzz"
|
msgstr "ZzzzZZzzzzZzzz"
|
||||||
|
|
||||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||||
msgstr "Olá, Eu sou Pwnagotchi! Iniciando ..."
|
msgstr "Olá, sou Pwnagotchi! Iniciando ..."
|
||||||
|
|
||||||
msgid "New day, new hunt, new pwns!"
|
msgid "New day, new hunt, new pwns!"
|
||||||
msgstr "Um novo dia, Uma nova caça e novos pwns!"
|
msgstr "Novo dia, Nova caçada, Novos pwns!"
|
||||||
|
|
||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr "Burle o Planeta!"
|
msgstr "Hackeie o Planeta!"
|
||||||
|
|
||||||
msgid "AI ready."
|
msgid "AI ready."
|
||||||
msgstr "IA pronta."
|
msgstr "IA pronta."
|
||||||
@ -36,64 +36,64 @@ msgid "The neural network is ready."
|
|||||||
msgstr "A rede neural está pronta."
|
msgstr "A rede neural está pronta."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr "Criando chaves, não desligue o sistema ..."
|
msgstr "Gerando chaves, não desligue ..."
|
||||||
|
|
||||||
#, 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 "Ei, canal {channel} está livre! Seu AP vai agradecer."
|
msgstr "Ei, o canal {channel} está livre! Seu AP vai agradecer."
|
||||||
|
|
||||||
msgid "Reading last session logs ..."
|
msgid "Reading last session logs ..."
|
||||||
msgstr "Lendo os logs da ultima sessão"
|
msgstr "Lendo os logs da última sessão ..."
|
||||||
|
|
||||||
#, 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 "Leia {lines_so_far} linha de logs até agora ..."
|
msgstr "Li {lines_so_far} linhas de logs até agora ..."
|
||||||
|
|
||||||
msgid "I'm bored ..."
|
msgid "I'm bored ..."
|
||||||
msgstr "Eu estou entediado ..."
|
msgstr "Estou entediado ..."
|
||||||
|
|
||||||
msgid "Let's go for a walk!"
|
msgid "Let's go for a walk!"
|
||||||
msgstr "Vamos ir numa caminhada!"
|
msgstr "Vamos dar um passeio!"
|
||||||
|
|
||||||
msgid "This is the best day of my life!"
|
msgid "This is the best day of my life!"
|
||||||
msgstr "Esse é o melhor dia da minha vida!"
|
msgstr "Este é o melhor dia da minha vida!"
|
||||||
|
|
||||||
msgid "Shitty day :/"
|
msgid "Shitty day :/"
|
||||||
msgstr "Dia ruim :/"
|
msgstr "Que dia ruim :/"
|
||||||
|
|
||||||
msgid "I'm extremely bored ..."
|
msgid "I'm extremely bored ..."
|
||||||
msgstr "Eu estou extremamente entediado ..."
|
msgstr "Estou extremamente entediado ..."
|
||||||
|
|
||||||
msgid "I'm very sad ..."
|
msgid "I'm very sad ..."
|
||||||
msgstr "Eu estou muito triste ..."
|
msgstr "Estou muito triste ..."
|
||||||
|
|
||||||
msgid "I'm sad"
|
msgid "I'm sad"
|
||||||
msgstr "Eu estou triste"
|
msgstr "Estou triste"
|
||||||
|
|
||||||
msgid "Leave me alone ..."
|
msgid "Leave me alone ..."
|
||||||
msgstr "Me deixe em paz ..."
|
msgstr "Me deixe em paz ..."
|
||||||
|
|
||||||
msgid "I'm mad at you!"
|
msgid "I'm mad at you!"
|
||||||
msgstr "Eu estou bravo com você!"
|
msgstr "Estou bravo com você!"
|
||||||
|
|
||||||
msgid "I'm living the life!"
|
msgid "I'm living the life!"
|
||||||
msgstr "Eu estou vivendo a vida!"
|
msgstr "Estou aproveitando a vida!"
|
||||||
|
|
||||||
msgid "I pwn therefore I am."
|
msgid "I pwn therefore I am."
|
||||||
msgstr "Eu pwn então Eu sou."
|
msgstr "Eu pwn, logo existo."
|
||||||
|
|
||||||
msgid "So many networks!!!"
|
msgid "So many networks!!!"
|
||||||
msgstr "Tantas redes!!!"
|
msgstr "Uau! Quantas redes!!"
|
||||||
|
|
||||||
msgid "I'm having so much fun!"
|
msgid "I'm having so much fun!"
|
||||||
msgstr "Eu estou tendo muita diversão"
|
msgstr "Estou me divertindo muito!"
|
||||||
|
|
||||||
msgid "My crime is that of curiosity ..."
|
msgid "My crime is that of curiosity ..."
|
||||||
msgstr "Meu crime é de curiosidade ..."
|
msgstr "Meu crime é a curiosidade ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hello {name}! Nice to meet you."
|
msgid "Hello {name}! Nice to meet you."
|
||||||
msgstr "Olá {name}! É bom em conhecê-lo"
|
msgstr "Olá {name}! Prazer em conhecê-lo."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Yo {name}! Sup?"
|
msgid "Yo {name}! Sup?"
|
||||||
@ -101,33 +101,33 @@ msgstr "Ei {name}! Como vai?"
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey {name} how are you doing?"
|
msgid "Hey {name} how are you doing?"
|
||||||
msgstr "Ei {name} como você está indo?"
|
msgstr "Ei {name}, como você está?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Unit {name} is nearby!"
|
msgid "Unit {name} is nearby!"
|
||||||
msgstr ""
|
msgstr "A unidade {name} está próxima!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uhm ... goodbye {name}"
|
msgid "Uhm ... goodbye {name}"
|
||||||
msgstr ""
|
msgstr "Hmm ... tchau {name}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} is gone ..."
|
msgid "{name} is gone ..."
|
||||||
msgstr ""
|
msgstr "{name} desapareceu ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Whoops ... {name} is gone."
|
msgid "Whoops ... {name} is gone."
|
||||||
msgstr ""
|
msgstr "Oops ... {name} desapareceu."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} missed!"
|
msgid "{name} missed!"
|
||||||
msgstr "{name} errou!"
|
msgstr "Perdi {name}!"
|
||||||
|
|
||||||
msgid "Missed!"
|
msgid "Missed!"
|
||||||
msgstr "Errei!"
|
msgstr "Perdi!"
|
||||||
|
|
||||||
msgid "Good friends are a blessing!"
|
msgid "Good friends are a blessing!"
|
||||||
msgstr "Bom amigos são uma bensão!"
|
msgstr "Bons amigos são uma bênção!"
|
||||||
|
|
||||||
msgid "I love my friends!"
|
msgid "I love my friends!"
|
||||||
msgstr "Eu amo meus amigos!"
|
msgstr "Eu amo meus amigos!"
|
||||||
@ -136,7 +136,7 @@ msgid "Nobody wants to play with me ..."
|
|||||||
msgstr "Ninguém quer brincar comigo ..."
|
msgstr "Ninguém quer brincar comigo ..."
|
||||||
|
|
||||||
msgid "I feel so alone ..."
|
msgid "I feel so alone ..."
|
||||||
msgstr "Estou me sentindo sozinho"
|
msgstr "Me sinto tão sozinho ..."
|
||||||
|
|
||||||
msgid "Where's everybody?!"
|
msgid "Where's everybody?!"
|
||||||
msgstr "Onde está todo mundo?!"
|
msgstr "Onde está todo mundo?!"
|
||||||
@ -160,27 +160,27 @@ msgstr "Zzz"
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Waiting for {secs}s ..."
|
msgid "Waiting for {secs}s ..."
|
||||||
msgstr "Esperando por {secs}s ..."
|
msgstr "Aguardando {secs}s ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Looking around ({secs}s)"
|
msgid "Looking around ({secs}s)"
|
||||||
msgstr "Olhando por volta ({secs}s)"
|
msgstr "Olhando em volta ... ({secs}s)"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey {what} let's be friends!"
|
msgid "Hey {what} let's be friends!"
|
||||||
msgstr "Ei {what} vamos ser amigos!"
|
msgstr "Ei {what}, vamos ser amigos!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Associating to {what}"
|
msgid "Associating to {what}"
|
||||||
msgstr "Associando para {what}"
|
msgstr "Associando a {what}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Yo {what}!"
|
msgid "Yo {what}!"
|
||||||
msgstr "Ei {what}!"
|
msgstr "Olá {what}!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Just decided that {mac} needs no WiFi!"
|
msgid "Just decided that {mac} needs no WiFi!"
|
||||||
msgstr "Apenas decidindo que {mac} não precisa de WiFi!"
|
msgstr "Acabei de decidir que {mac} não precisa de WiFi!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Deauthenticating {mac}"
|
msgid "Deauthenticating {mac}"
|
||||||
@ -192,11 +192,11 @@ msgstr "Banindo {mac}!"
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Cool, we got {num} new handshake{plural}!"
|
msgid "Cool, we got {num} new handshake{plural}!"
|
||||||
msgstr "Legal, conseguimos {num} novos handshake{plural}!"
|
msgstr "Legal, conseguimos {num} novo{plural} handshake{plural}!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "You have {count} new message{plural}!"
|
msgid "You have {count} new message{plural}!"
|
||||||
msgstr "Você tem {count} novas messagem{plural}!"
|
msgstr "Você tem {count} nova{plural} messagem{plural}!"
|
||||||
|
|
||||||
msgid "Oops, something went wrong ... Rebooting ..."
|
msgid "Oops, something went wrong ... Rebooting ..."
|
||||||
msgstr "Oops, algo deu errado ... Reiniciando ..."
|
msgstr "Oops, algo deu errado ... Reiniciando ..."
|
||||||
@ -207,7 +207,7 @@ msgstr "Enviando dados para {to} ..."
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Downloading from {name} ..."
|
msgid "Downloading from {name} ..."
|
||||||
msgstr "Instalando para {name} ..."
|
msgstr "Baixando de {name} ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Kicked {num} stations\n"
|
msgid "Kicked {num} stations\n"
|
||||||
@ -225,11 +225,11 @@ msgid "Got {num} handshakes\n"
|
|||||||
msgstr "Peguei {num} handshakes\n"
|
msgstr "Peguei {num} handshakes\n"
|
||||||
|
|
||||||
msgid "Met 1 peer"
|
msgid "Met 1 peer"
|
||||||
msgstr "Encontrei 1 pessoa"
|
msgstr "Conheci 1 peer"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Met {num} peers"
|
msgid "Met {num} peers"
|
||||||
msgstr "Encontrei {num} pessoas"
|
msgstr "Conheci {num} peers"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -237,8 +237,8 @@ msgid ""
|
|||||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Estou navegando há {duration} e expulsei {deauthed} clientes! Também conheci "
|
"Estou pwning há {duration} e expulsei {deauthed} clientes! Também conheci "
|
||||||
"{associamos} novos amigos e comi {handshakes} handshakes! #pwnagotchi "
|
"{associated} novos amigos e comi {handshakes} handshakes! #pwnagotchi "
|
||||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
|
|
||||||
msgid "hours"
|
msgid "hours"
|
||||||
|
BIN
pwnagotchi/locale/rs/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/rs/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
260
pwnagotchi/locale/rs/LC_MESSAGES/voice.po
Normal file
260
pwnagotchi/locale/rs/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <dragan.miljkovic29@gmail.com>, 2024.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Srpski prevod v1\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2024-02-16 15:26-0300\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: RS <dragan.miljkovic29@gmail.com>\n"
|
||||||
|
"Language: Serbian\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
msgid "ZzzzZZzzzzZzzz"
|
||||||
|
msgstr "ZzzzZZzzzzZzzz"
|
||||||
|
|
||||||
|
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||||
|
msgstr "Zdravo, ja sam Pwnagotchi! Započinjem ..."
|
||||||
|
|
||||||
|
msgid "New day, new hunt, new pwns!"
|
||||||
|
msgstr "Novi dan, novi lov, novi pnwovi!"
|
||||||
|
|
||||||
|
msgid "Hack the Planet!"
|
||||||
|
msgstr "Hakuj Planetu!"
|
||||||
|
|
||||||
|
msgid "AI ready."
|
||||||
|
msgstr "AI spreman."
|
||||||
|
|
||||||
|
msgid "The neural network is ready."
|
||||||
|
msgstr "Neuronska mreža je spremna."
|
||||||
|
|
||||||
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
msgstr "Generišem ključeve, ne isključuj me..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||||
|
msgstr "Hej, kanal {channel} je slobodan! Tvoj AP će ti se zahvaliti."
|
||||||
|
|
||||||
|
msgid "Reading last session logs ..."
|
||||||
|
msgstr "Čitanje logova poslednje sesije ..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Read {lines_so_far} log lines so far ..."
|
||||||
|
msgstr "Pročitao {lines_so_far} log linija do sada ..."
|
||||||
|
|
||||||
|
msgid "I'm bored ..."
|
||||||
|
msgstr "Dosadno mi je ..."
|
||||||
|
|
||||||
|
msgid "Let's go for a walk!"
|
||||||
|
msgstr "Hajmo u šetnju!"
|
||||||
|
|
||||||
|
msgid "This is the best day of my life!"
|
||||||
|
msgstr "Ovo je najbolji dan mog života!"
|
||||||
|
|
||||||
|
msgid "Shitty day :/"
|
||||||
|
msgstr "Sranje dan :/"
|
||||||
|
|
||||||
|
msgid "I'm extremely bored ..."
|
||||||
|
msgstr "Užasno mi je dosadno ..."
|
||||||
|
|
||||||
|
msgid "I'm very sad ..."
|
||||||
|
msgstr "Jako sam tužan ..."
|
||||||
|
|
||||||
|
msgid "I'm sad"
|
||||||
|
msgstr "Tužan sam"
|
||||||
|
|
||||||
|
msgid "Leave me alone ..."
|
||||||
|
msgstr "Ostavi me na miru ..."
|
||||||
|
|
||||||
|
msgid "I'm mad at you!"
|
||||||
|
msgstr "Ljut sam na tebe!"
|
||||||
|
|
||||||
|
msgid "I'm living the life!"
|
||||||
|
msgstr "Živim ga!"
|
||||||
|
|
||||||
|
msgid "I pwn therefore I am."
|
||||||
|
msgstr "Ja pwnujem dakle postojim."
|
||||||
|
|
||||||
|
msgid "So many networks!!!"
|
||||||
|
msgstr "Tako mnogo mreža!!!"
|
||||||
|
|
||||||
|
msgid "I'm having so much fun!"
|
||||||
|
msgstr "Previše se zabavljam!"
|
||||||
|
|
||||||
|
msgid "My crime is that of curiosity ..."
|
||||||
|
msgstr "Moj zločin je radoznalost ..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Hello {name}! Nice to meet you."
|
||||||
|
msgstr "Zdravo {name}! Drago mi je da te upoznam."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Yo {name}! Sup?"
|
||||||
|
msgstr "Ej {name}! Šta ima?"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Hey {name} how are you doing?"
|
||||||
|
msgstr "Hej {name} kako si?"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unit {name} is nearby!"
|
||||||
|
msgstr "Jedinica {name} je blizu!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Uhm ... goodbye {name}"
|
||||||
|
msgstr "Umm ... doviđenja {name}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "{name} is gone ..."
|
||||||
|
msgstr "{name} je nestao ..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Whoops ... {name} is gone."
|
||||||
|
msgstr "Ups ... {name} je nestao."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "{name} missed!"
|
||||||
|
msgstr "{name} promašen!"
|
||||||
|
|
||||||
|
msgid "Missed!"
|
||||||
|
msgstr "Promašen!"
|
||||||
|
|
||||||
|
msgid "Good friends are a blessing!"
|
||||||
|
msgstr "Dobri prijatelji su blagoslov!"
|
||||||
|
|
||||||
|
msgid "I love my friends!"
|
||||||
|
msgstr "Volim svoje prijatelje!"
|
||||||
|
|
||||||
|
msgid "Nobody wants to play with me ..."
|
||||||
|
msgstr "Niko ne želi da se igra sa mnom ..."
|
||||||
|
|
||||||
|
msgid "I feel so alone ..."
|
||||||
|
msgstr "Osećam se toliko usamljeno ..."
|
||||||
|
|
||||||
|
msgid "Where's everybody?!"
|
||||||
|
msgstr "Gde su svi?!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Napping for {secs}s ..."
|
||||||
|
msgstr "Dremam {secs}s ..."
|
||||||
|
|
||||||
|
msgid "Zzzzz"
|
||||||
|
msgstr "Zzzzz"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "ZzzZzzz ({secs}s)"
|
||||||
|
msgstr "ZzzZzzz ({secs}s)"
|
||||||
|
|
||||||
|
msgid "Good night."
|
||||||
|
msgstr "Laku noć."
|
||||||
|
|
||||||
|
msgid "Zzz"
|
||||||
|
msgstr "Zzz"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Waiting for {secs}s ..."
|
||||||
|
msgstr "Čekam {secs}s ..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Looking around ({secs}s)"
|
||||||
|
msgstr "Gledam unaokolo ({secs}s)"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Hey {what} let's be friends!"
|
||||||
|
msgstr "Hej {what} hajde da budemo prijatelji!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Associating to {what}"
|
||||||
|
msgstr "Povezujem se sa {what}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Yo {what}!"
|
||||||
|
msgstr "Ej {what}!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Just decided that {mac} needs no WiFi!"
|
||||||
|
msgstr "Upravo sam odlučio da {mac} ne zahteva WiFi!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Deauthenticating {mac}"
|
||||||
|
msgstr "Deauthenticating {mac}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Kickbanning {mac}!"
|
||||||
|
msgstr "Kickbanning {mac}!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Cool, we got {num} new handshake{plural}!"
|
||||||
|
msgstr "Kul, imamo {num} novih handshakeova"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "You have {count} new message{plural}!"
|
||||||
|
msgstr "Imas {count} novih poruka"
|
||||||
|
|
||||||
|
msgid "Oops, something went wrong ... Rebooting ..."
|
||||||
|
msgstr "Ups, nešto je poslo po zlu ... Restartujem ..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Uploading data to {to} ..."
|
||||||
|
msgstr "Otpremam podatke na {to} ..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Downloading from {name} ..."
|
||||||
|
msgstr "Preuzimam od {name} ..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Kicked {num} stations\n"
|
||||||
|
msgstr "Kickovano {num} stanica\n"
|
||||||
|
|
||||||
|
msgid "Made >999 new friends\n"
|
||||||
|
msgstr "Stekao sam >999 novih prijatelja\n"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Made {num} new friends\n"
|
||||||
|
msgstr "Stekao sam {num} novih prijatelja\n"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Got {num} handshakes\n"
|
||||||
|
msgstr "Imam {num} handshakeova\n"
|
||||||
|
|
||||||
|
msgid "Met 1 peer"
|
||||||
|
msgstr "Sreo 1 peera"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Met {num} peers"
|
||||||
|
msgstr "Sreo {num} peerova"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid ""
|
||||||
|
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||||
|
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||||
|
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
|
msgstr ""
|
||||||
|
"Pwnujem već {duration} i kickovao sam {deauthed} klijenata! Takođe sam sreo "
|
||||||
|
"{associated} novih prijatelja i pojeo {handshakes} handshakeova! #pwnagotchi "
|
||||||
|
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
|
|
||||||
|
msgid "hours"
|
||||||
|
msgstr "sati"
|
||||||
|
|
||||||
|
msgid "minutes"
|
||||||
|
msgstr "minuta"
|
||||||
|
|
||||||
|
msgid "seconds"
|
||||||
|
msgstr "sekundi"
|
||||||
|
|
||||||
|
msgid "hour"
|
||||||
|
msgstr "sat"
|
||||||
|
|
||||||
|
msgid "minute"
|
||||||
|
msgstr "minut"
|
||||||
|
|
||||||
|
msgid "second"
|
||||||
|
msgstr "sekunda"
|
Binary file not shown.
@ -9,8 +9,8 @@ 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: 2023-11-16 21:10+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: 2024-03-27 18:40+0800\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: AlanLeung <admin@mcnot.pro>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"Language: Twi\n"
|
"Language: Twi\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@ -18,218 +18,218 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "ZzzzZZzzzzZzzz"
|
msgid "ZzzzZZzzzzZzzz"
|
||||||
msgstr ""
|
msgstr "ZzzzZZzzzzZzzz"
|
||||||
|
|
||||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||||
msgstr ""
|
msgstr "HI!我是Pwnagotchi!\n程式啟動..."
|
||||||
|
|
||||||
msgid "New day, new hunt, new pwns!"
|
msgid "New day, new hunt, new pwns!"
|
||||||
msgstr ""
|
msgstr "新的一天!\n新的狩獵!新的入侵!"
|
||||||
|
|
||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr "我要駭入\n地球的所有人!"
|
||||||
|
|
||||||
msgid "AI ready."
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr "人工智慧已啟動。"
|
||||||
|
|
||||||
msgid "The neural network is ready."
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr "神經網路已啟動。"
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr ""
|
msgstr "產生金鑰中,\n請勿關閉..."
|
||||||
|
|
||||||
#, 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 ""
|
msgstr "嘿,{channel}很順暢!\n你的WIFI會感謝你的。"
|
||||||
|
|
||||||
msgid "Reading last session logs ..."
|
msgid "Reading last session logs ..."
|
||||||
msgstr ""
|
msgstr "正在閱讀最後的會話紀錄..."
|
||||||
|
|
||||||
#, 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 "目前已經閱讀了 {lines_so_far} 行的紀錄..."
|
||||||
|
|
||||||
msgid "I'm bored ..."
|
msgid "I'm bored ..."
|
||||||
msgstr ""
|
msgstr "我好無聊..."
|
||||||
|
|
||||||
msgid "Let's go for a walk!"
|
msgid "Let's go for a walk!"
|
||||||
msgstr ""
|
msgstr "我們! 散步! 散步散步散步散步"
|
||||||
|
|
||||||
msgid "This is the best day of my life!"
|
msgid "This is the best day of my life!"
|
||||||
msgstr ""
|
msgstr "這是我生命中最棒的一天!"
|
||||||
|
|
||||||
msgid "Shitty day :/"
|
msgid "Shitty day :/"
|
||||||
msgstr ""
|
msgstr "糟糕的一天 :/"
|
||||||
|
|
||||||
msgid "I'm extremely bored ..."
|
msgid "I'm extremely bored ..."
|
||||||
msgstr ""
|
msgstr "我超無聊的...炒雞 炒雞的那種"
|
||||||
|
|
||||||
msgid "I'm very sad ..."
|
msgid "I'm very sad ..."
|
||||||
msgstr ""
|
msgstr "我好難過..."
|
||||||
|
|
||||||
msgid "I'm sad"
|
msgid "I'm sad"
|
||||||
msgstr ""
|
msgstr "嗚嗚嗚...."
|
||||||
|
|
||||||
msgid "Leave me alone ..."
|
msgid "Leave me alone ..."
|
||||||
msgstr ""
|
msgstr "尼奏凱啦臭臭"
|
||||||
|
|
||||||
msgid "I'm mad at you!"
|
msgid "I'm mad at you!"
|
||||||
msgstr ""
|
msgstr "喔氣氣氣氣氣ˋ^ˊ"
|
||||||
|
|
||||||
msgid "I'm living the life!"
|
msgid "I'm living the life!"
|
||||||
msgstr ""
|
msgstr "真是充實的一生!"
|
||||||
|
|
||||||
msgid "I pwn therefore I am."
|
msgid "I pwn therefore I am."
|
||||||
msgstr ""
|
msgstr "我駭故我在."
|
||||||
|
|
||||||
msgid "So many networks!!!"
|
msgid "So many networks!!!"
|
||||||
msgstr ""
|
msgstr "好多網路啊!!!吃! 吃他! 吃光光!!!"
|
||||||
|
|
||||||
msgid "I'm having so much fun!"
|
msgid "I'm having so much fun!"
|
||||||
msgstr ""
|
msgstr "我玩的超級開心!"
|
||||||
|
|
||||||
msgid "My crime is that of curiosity ..."
|
msgid "My crime is that of curiosity ..."
|
||||||
msgstr ""
|
msgstr "我的缺點就是\n太好奇了..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hello {name}! Nice to meet you."
|
msgid "Hello {name}! Nice to meet you."
|
||||||
msgstr ""
|
msgstr "尼豪{name}!\n很高興認識你!!!!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Yo {name}! Sup?"
|
msgid "Yo {name}! Sup?"
|
||||||
msgstr ""
|
msgstr "嗨 {name}! 你來攻打我的村莊?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey {name} how are you doing?"
|
msgid "Hey {name} how are you doing?"
|
||||||
msgstr ""
|
msgstr "嗨 {name} 你最近過得如何˙ˇ˙?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Unit {name} is nearby!"
|
msgid "Unit {name} is nearby!"
|
||||||
msgstr ""
|
msgstr "{name}\n就在附近!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uhm ... goodbye {name}"
|
msgid "Uhm ... goodbye {name}"
|
||||||
msgstr ""
|
msgstr "哦嗚 ... \n拜拜{name}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} is gone ..."
|
msgid "{name} is gone ..."
|
||||||
msgstr ""
|
msgstr "{name}\n不見了 ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Whoops ... {name} is gone."
|
msgid "Whoops ... {name} is gone."
|
||||||
msgstr ""
|
msgstr "哦歐...\n{name}\n不見了。"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} missed!"
|
msgid "{name} missed!"
|
||||||
msgstr ""
|
msgstr "我剛剛錯過了{name}!"
|
||||||
|
|
||||||
msgid "Missed!"
|
msgid "Missed!"
|
||||||
msgstr ""
|
msgstr "又錯過了!"
|
||||||
|
|
||||||
msgid "Good friends are a blessing!"
|
msgid "Good friends are a blessing!"
|
||||||
msgstr ""
|
msgstr "有個好朋友\n真幸福!"
|
||||||
|
|
||||||
msgid "I love my friends!"
|
msgid "I love my friends!"
|
||||||
msgstr ""
|
msgstr "我喜歡\n我的朋友!"
|
||||||
|
|
||||||
msgid "Nobody wants to play with me ..."
|
msgid "Nobody wants to play with me ..."
|
||||||
msgstr ""
|
msgstr "沒人想跟我玩..."
|
||||||
|
|
||||||
msgid "I feel so alone ..."
|
msgid "I feel so alone ..."
|
||||||
msgstr ""
|
msgstr "我覺得好孤單..."
|
||||||
|
|
||||||
msgid "Where's everybody?!"
|
msgid "Where's everybody?!"
|
||||||
msgstr ""
|
msgstr "大家都去哪裡了?!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Napping for {secs}s ..."
|
msgid "Napping for {secs}s ..."
|
||||||
msgstr ""
|
msgstr "我想瞇{secs}秒一下..."
|
||||||
|
|
||||||
msgid "Zzzzz"
|
msgid "Zzzzz"
|
||||||
msgstr ""
|
msgstr "Zzzzz"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "ZzzZzzz ({secs}s)"
|
msgid "ZzzZzzz ({secs}s)"
|
||||||
msgstr ""
|
msgstr "ZzzZzzz({secs}秒)"
|
||||||
|
|
||||||
msgid "Good night."
|
msgid "Good night."
|
||||||
msgstr ""
|
msgstr "晚安!"
|
||||||
|
|
||||||
msgid "Zzz"
|
msgid "Zzz"
|
||||||
msgstr ""
|
msgstr "Zzz"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Waiting for {secs}s ..."
|
msgid "Waiting for {secs}s ..."
|
||||||
msgstr ""
|
msgstr "等我{secs}秒..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Looking around ({secs}s)"
|
msgid "Looking around ({secs}s)"
|
||||||
msgstr ""
|
msgstr "環顧四周({secs}秒)"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey {what} let's be friends!"
|
msgid "Hey {what} let's be friends!"
|
||||||
msgstr ""
|
msgstr "嗨\n{what}\n讓我們來當朋友吧!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Associating to {what}"
|
msgid "Associating to {what}"
|
||||||
msgstr ""
|
msgstr "正在連接\n{what}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Yo {what}!"
|
msgid "Yo {what}!"
|
||||||
msgstr ""
|
msgstr "喲,\n{what}!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Just decided that {mac} needs no WiFi!"
|
msgid "Just decided that {mac} needs no WiFi!"
|
||||||
msgstr ""
|
msgstr "我要讓\n{mac}\n斷線!\n他不需要上網!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Deauthenticating {mac}"
|
msgid "Deauthenticating {mac}"
|
||||||
msgstr ""
|
msgstr "解除\n{mac}\n的授權中"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Kickbanning {mac}!"
|
msgid "Kickbanning {mac}!"
|
||||||
msgstr ""
|
msgstr "把\n{mac}\n踢出中!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Cool, we got {num} new handshake{plural}!"
|
msgid "Cool, we got {num} new handshake{plural}!"
|
||||||
msgstr ""
|
msgstr "酷耶,我們抓到{num}個\n新的握手包{plural}!"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "You have {count} new message{plural}!"
|
msgid "You have {count} new message{plural}!"
|
||||||
msgstr ""
|
msgstr "你有{count}個新訊息{plural}!"
|
||||||
|
|
||||||
msgid "Oops, something went wrong ... Rebooting ..."
|
msgid "Oops, something went wrong ... Rebooting ..."
|
||||||
msgstr ""
|
msgstr "哦歐,有些地方出錯了...\n重新啟動中..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uploading data to {to} ..."
|
msgid "Uploading data to {to} ..."
|
||||||
msgstr ""
|
msgstr "正在上傳資料到 {to} ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Downloading from {name} ..."
|
msgid "Downloading from {name} ..."
|
||||||
msgstr ""
|
msgstr "正在從 {name} 下載資料..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Kicked {num} stations\n"
|
msgid "Kicked {num} stations\n"
|
||||||
msgstr ""
|
msgstr "踢了 {num} 個設備\n"
|
||||||
|
|
||||||
msgid "Made >999 new friends\n"
|
msgid "Made >999 new friends\n"
|
||||||
msgstr ""
|
msgstr "交了 >999 個新朋友\n"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Made {num} new friends\n"
|
msgid "Made {num} new friends\n"
|
||||||
msgstr ""
|
msgstr "交了 {num} 個新朋友\n"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Got {num} handshakes\n"
|
msgid "Got {num} handshakes\n"
|
||||||
msgstr ""
|
msgstr "捕獲了 {num} 個握手包\n"
|
||||||
|
|
||||||
msgid "Met 1 peer"
|
msgid "Met 1 peer"
|
||||||
msgstr ""
|
msgstr "遇到了 1 個同好"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Met {num} peers"
|
msgid "Met {num} peers"
|
||||||
msgstr ""
|
msgstr "遇到了 {num} 個同好"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -237,21 +237,24 @@ msgid ""
|
|||||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"我花了{duration}的時間\n駭入和踢了{deauthed}好多設備."
|
||||||
|
"我還交了好多{associated}新朋友,\n而且抓到了{handshakes}握手包!"
|
||||||
|
"#pwnagotchi#入侵日志 #駭客人生 #入侵整個星球 #天網 #我好棒˙ˇ˙"
|
||||||
|
|
||||||
msgid "hours"
|
msgid "hours"
|
||||||
msgstr ""
|
msgstr "時"
|
||||||
|
|
||||||
msgid "minutes"
|
msgid "minutes"
|
||||||
msgstr ""
|
msgstr "分"
|
||||||
|
|
||||||
msgid "seconds"
|
msgid "seconds"
|
||||||
msgstr ""
|
msgstr "秒"
|
||||||
|
|
||||||
msgid "hour"
|
msgid "hour"
|
||||||
msgstr ""
|
msgstr "時"
|
||||||
|
|
||||||
msgid "minute"
|
msgid "minute"
|
||||||
msgstr ""
|
msgstr "分"
|
||||||
|
|
||||||
msgid "second"
|
msgid "second"
|
||||||
msgstr ""
|
msgstr "秒"
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
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: 2023-11-17 15:46+0100\n"
|
"POT-Creation-Date: 2024-11-17 20:51+0100\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"
|
||||||
|
@ -217,24 +217,44 @@ class LastSession(object):
|
|||||||
def setup_logging(args, config):
|
def setup_logging(args, config):
|
||||||
cfg = config['main']['log']
|
cfg = config['main']['log']
|
||||||
filename = cfg['path']
|
filename = cfg['path']
|
||||||
|
filenameDebug = cfg['path-debug']
|
||||||
|
|
||||||
formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s")
|
#global formatter
|
||||||
root = logging.getLogger()
|
formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] [%(threadName)s] : %(message)s")
|
||||||
|
logger = logging.getLogger()
|
||||||
|
|
||||||
root.setLevel(logging.DEBUG if args.debug else logging.INFO)
|
for handler in logger.handlers:
|
||||||
|
handler.setLevel(logging.DEBUG if args.debug else logging.INFO)
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
|
||||||
|
logger.setLevel(logging.DEBUG if args.debug else logging.INFO)
|
||||||
|
|
||||||
if filename:
|
if filename:
|
||||||
# since python default log rotation might break session data in different files,
|
# since python default log rotation might break session data in different files,
|
||||||
# we need to do log rotation ourselves
|
# we need to do log rotation ourselves
|
||||||
log_rotation(filename, cfg)
|
log_rotation(filename, cfg)
|
||||||
|
log_rotation(filenameDebug, cfg)
|
||||||
|
|
||||||
file_handler = logging.FileHandler(filename)
|
|
||||||
file_handler.setFormatter(formatter)
|
|
||||||
root.addHandler(file_handler)
|
|
||||||
|
|
||||||
console_handler = logging.StreamHandler()
|
|
||||||
console_handler.setFormatter(formatter)
|
# File handler for logging all normal messages
|
||||||
root.addHandler(console_handler)
|
file_handler = logging.FileHandler(filename) #creates new
|
||||||
|
file_handler.setLevel(logging.INFO)
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
# File handler for logging all debug messages
|
||||||
|
file_handler = logging.FileHandler(filenameDebug) #creates new
|
||||||
|
file_handler.setLevel(logging.DEBUG)
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
# Console handler for logging debug messages if args.debug is true else just log normal
|
||||||
|
#console_handler = logging.StreamHandler() #creates new
|
||||||
|
#console_handler.setLevel(logging.DEBUG if args.debug else logging.INFO)
|
||||||
|
#console_handler.setFormatter(formatter)
|
||||||
|
#logger.addHandler(console_handler)
|
||||||
|
|
||||||
if not args.debug:
|
if not args.debug:
|
||||||
# disable scapy and tensorflow logging
|
# disable scapy and tensorflow logging
|
||||||
@ -250,6 +270,8 @@ def setup_logging(args, config):
|
|||||||
requests_log.prpagate = False
|
requests_log.prpagate = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def log_rotation(filename, cfg):
|
def log_rotation(filename, cfg):
|
||||||
rotation = cfg['rotation']
|
rotation = cfg['rotation']
|
||||||
if not rotation['enabled']:
|
if not rotation['enabled']:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import _thread
|
#import _thread
|
||||||
|
import threading
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -41,7 +42,8 @@ class AsyncAdvertiser(object):
|
|||||||
|
|
||||||
def start_advertising(self):
|
def start_advertising(self):
|
||||||
if self._config['personality']['advertise']:
|
if self._config['personality']['advertise']:
|
||||||
_thread.start_new_thread(self._adv_poller, ())
|
#_thread.start_new_thread(self._adv_poller, ())
|
||||||
|
threading.Thread(target=self._adv_poller,args=(), name="Grid", daemon=True).start()
|
||||||
|
|
||||||
grid.set_advertisement_data(self._advertisement)
|
grid.set_advertisement_data(self._advertisement)
|
||||||
grid.advertise(True)
|
grid.advertise(True)
|
||||||
|
@ -6,6 +6,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import pwnagotchi.grid
|
import pwnagotchi.grid
|
||||||
|
import prctl
|
||||||
|
|
||||||
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
|
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
|
||||||
loaded = {}
|
loaded = {}
|
||||||
@ -72,6 +73,7 @@ def toggle_plugin(name, enable=True):
|
|||||||
|
|
||||||
def on(event_name, *args, **kwargs):
|
def on(event_name, *args, **kwargs):
|
||||||
for plugin_name in loaded.keys():
|
for plugin_name in loaded.keys():
|
||||||
|
|
||||||
one(plugin_name, event_name, *args, **kwargs)
|
one(plugin_name, event_name, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@ -82,7 +84,10 @@ def locked_cb(lock_name, cb, *args, **kwargs):
|
|||||||
locks[lock_name] = threading.Lock()
|
locks[lock_name] = threading.Lock()
|
||||||
|
|
||||||
with locks[lock_name]:
|
with locks[lock_name]:
|
||||||
cb(*args, *kwargs)
|
# Setting the thread name using prctl
|
||||||
|
plugin_name, plugin_cb = lock_name.split("::")
|
||||||
|
prctl.set_name(f"{plugin_name}.{plugin_cb}")
|
||||||
|
cb(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def one(plugin_name, event_name, *args, **kwargs):
|
def one(plugin_name, event_name, *args, **kwargs):
|
||||||
@ -95,8 +100,10 @@ def one(plugin_name, event_name, *args, **kwargs):
|
|||||||
if callback is not None and callable(callback):
|
if callback is not None and callable(callback):
|
||||||
try:
|
try:
|
||||||
lock_name = "%s::%s" % (plugin_name, cb_name)
|
lock_name = "%s::%s" % (plugin_name, cb_name)
|
||||||
locked_cb_args = (lock_name, callback, *args, *kwargs)
|
thread_name = f'{plugin_name}.{cb_name}'
|
||||||
_thread.start_new_thread(locked_cb, locked_cb_args)
|
thread = threading.Thread(target=locked_cb, args=(lock_name, callback, *args, *kwargs), name=thread_name, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
|
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
|
||||||
logging.error(e, exc_info=True)
|
logging.error(e, exc_info=True)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -130,7 +139,7 @@ def install(display, update):
|
|||||||
source_path = "%s-%s" % (source_path, update['available'])
|
source_path = "%s-%s" % (source_path, update['available'])
|
||||||
|
|
||||||
# setup.py is going to install data files for us
|
# setup.py is going to install data files for us
|
||||||
os.system("cd %s && pip3 install . --no-cache-dir --break-system-packages" % source_path)
|
os.system("cd %s && pip3 install . --break-system-packages" % source_path)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -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)
|
||||||
|
@ -578,7 +578,7 @@ class BTTether(plugins.Plugin):
|
|||||||
def on_ui_setup(self, ui):
|
def on_ui_setup(self, ui):
|
||||||
with ui._lock:
|
with ui._lock:
|
||||||
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-',
|
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-',
|
||||||
position=(ui.width() / 2 - 10, 0),
|
position=(ui.width() / 2 - 20, 0),
|
||||||
label_font=fonts.Bold, text_font=fonts.Medium))
|
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||||
|
|
||||||
def on_ui_update(self, ui):
|
def on_ui_update(self, ui):
|
||||||
|
@ -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,23 +26,19 @@ 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.
|
||||||
"""
|
"""
|
||||||
__dependencies__ = {
|
|
||||||
'pip': ['scapy']
|
|
||||||
}
|
|
||||||
__defaults__ = {
|
|
||||||
'enabled': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.options = dict()
|
self.options = dict()
|
||||||
self.pattern = re.compile(r'brcmf_cfg80211_nexmon_set_channel.*?Set Channel failed')
|
self.pattern = re.compile(r'ieee80211 phy0: brcmf_cfg80211_add_iface: iface validation failed: err=-95')
|
||||||
self.pattern2 = re.compile(r'wifi error while hopping to channel')
|
self.pattern2 = re.compile(r'wifi error while hopping to channel')
|
||||||
self.pattern3 = re.compile(r'Firmware has halted or crashed')
|
self.pattern3 = re.compile(r'Firmware has halted or crashed')
|
||||||
self.pattern4 = re.compile(r'error 400: could not find interface wlan0mon')
|
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.pattern7 = re.compile(r'ieee80211 phy0: _brcmf_set_multicast_list: Setting allmulti failed, -110')
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
@ -50,23 +51,9 @@ class FixServices(plugins.Plugin):
|
|||||||
stdout=subprocess.PIPE).stdout))[-10:])
|
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.info("[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.")
|
||||||
|
|
||||||
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.info('[Fix_Services] Blind-Bug detected. Restarting.')
|
|
||||||
try:
|
|
||||||
self._tryTurningItOffAndOnAgain(agent)
|
|
||||||
except Exception as err:
|
|
||||||
logging.warning("[Fix_Services turnOffAndOn] %s" % repr(err))
|
|
||||||
|
|
||||||
else:
|
|
||||||
logging.info("[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))
|
||||||
@ -80,12 +67,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:
|
||||||
@ -105,42 +92,34 @@ class FixServices(plugins.Plugin):
|
|||||||
other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'],
|
other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'],
|
||||||
stdout=subprocess.PIPE).stdout))[-10:])
|
stdout=subprocess.PIPE).stdout))[-10:])
|
||||||
other_other_last_lines = ''.join(
|
other_other_last_lines = ''.join(
|
||||||
list(TextIOWrapper(subprocess.Popen(['tail', '-n10', '/home/pi/logs/pwnagotchi.log'],
|
list(TextIOWrapper(subprocess.Popen(['tail', '-n10', '/etc/pwnagotchi/log/pwnagotchi.log'],
|
||||||
stdout=subprocess.PIPE).stdout))[-10:])
|
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:
|
||||||
# get last 10 lines
|
# get last 10 lines
|
||||||
display = None
|
display = agent.view()
|
||||||
|
|
||||||
logging.debug("[Fix_Services]**** checking")
|
logging.debug("[Fix_Services]**** checking")
|
||||||
|
if len(self.pattern.findall(last_lines)) >= 1:
|
||||||
# Look for pattern 1
|
subprocess.check_output("monstop", shell=True)
|
||||||
if len(self.pattern.findall(last_lines)) >= 3:
|
subprocess.check_output("monstart", shell=True)
|
||||||
logging.info("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
display.set('status', 'Wifi channel stuck. Restarting recon.')
|
||||||
if hasattr(agent, 'view'):
|
display.update(force=True)
|
||||||
display = agent.view()
|
pwnagotchi.restart("AUTO")
|
||||||
display.set('status', 'Blind-Bug detected. Restarting.')
|
|
||||||
display.update(force=True)
|
|
||||||
logging.info('[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
|
# Look for pattern 2
|
||||||
elif len(self.pattern2.findall(other_last_lines)) >= 5:
|
elif len(self.pattern2.findall(other_last_lines)) >= 5:
|
||||||
logging.info("[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.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})
|
||||||
@ -154,32 +133,65 @@ class FixServices(plugins.Plugin):
|
|||||||
|
|
||||||
# Look for pattern 3
|
# Look for pattern 3
|
||||||
elif len(self.pattern3.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.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
|
display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
|
||||||
display.update(force=True)
|
display.update(force=True)
|
||||||
try:
|
try:
|
||||||
# Run the monstart command to restart wlan0mon
|
# Run the monstart command to restart wlan0mon
|
||||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||||
logging.info("[Fix_Services monstart]: %s" % repr(cmd_output))
|
logging.debug("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||||
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 4
|
# Look for pattern 4
|
||||||
elif len(self.pattern4.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.set('status', 'Restarting wlan0 now!')
|
display.set('status', 'Restarting wlan0 now!')
|
||||||
display.update(force=True)
|
display.update(force=True)
|
||||||
try:
|
try:
|
||||||
# Run the monstart command to restart wlan0mon
|
# Run the monstart command to restart wlan0mon
|
||||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||||
logging.info("[Fix_Services monstart]: %s" % repr(cmd_output))
|
logging.debug("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||||
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.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.set('status', 'Restarting pwnagotchi!')
|
||||||
|
display.update(force=True)
|
||||||
|
os.system("systemctl restart bettercap")
|
||||||
|
pwnagotchi.restart("AUTO")
|
||||||
|
|
||||||
|
# Look for pattern 7
|
||||||
|
elif len(self.pattern7.findall(other_other_last_lines)) >= 1:
|
||||||
|
logging.debug("[Fix_Services] Monitor mode failed!")
|
||||||
|
try:
|
||||||
|
result = agent.run("wifi.recon off; wifi.recon on")
|
||||||
|
if result["success"]:
|
||||||
|
logging.debug("[Fix_Services] wifi.recon flip: success!")
|
||||||
|
if display:
|
||||||
|
display.update(force=True, new_data={"status": "Wifi recon flipped!",
|
||||||
|
"face": faces.COOL})
|
||||||
|
else:
|
||||||
|
print("Wifi recon flipped\nthat was easy!")
|
||||||
|
else:
|
||||||
|
logging.warning("[Fix_Services] wifi.recon flip: FAILED: %s" % repr(result))
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
logging.error("[Fix_Services wifi.recon flip] %s" % repr(err))
|
||||||
else:
|
else:
|
||||||
print("logs look good")
|
print("logs look good")
|
||||||
|
|
||||||
@ -192,7 +204,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)
|
||||||
@ -207,7 +219,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()
|
||||||
@ -232,9 +244,9 @@ class FixServices(plugins.Plugin):
|
|||||||
# is it up?
|
# is it up?
|
||||||
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.info("[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
|
||||||
@ -255,7 +267,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)
|
||||||
@ -271,14 +283,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:
|
||||||
@ -286,28 +297,24 @@ 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.info(
|
logging.debug(
|
||||||
"[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
|
"[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.info(
|
logging.debug(
|
||||||
"[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err))
|
"[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err))
|
||||||
except Exception as cerr: #
|
except Exception as cerr: #
|
||||||
if not display:
|
if not display:
|
||||||
@ -326,11 +333,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
|
||||||
@ -340,14 +347,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")
|
||||||
|
|
||||||
@ -357,7 +364,7 @@ class FixServices(plugins.Plugin):
|
|||||||
"face": faces.HAPPY})
|
"face": faces.HAPPY})
|
||||||
else:
|
else:
|
||||||
print("I can see again")
|
print("I can see again")
|
||||||
logging.info("[Fix_Services] wifi.recon on")
|
logging.debug("[Fix_Services] wifi.recon on")
|
||||||
self.LASTTRY = time.time() + 120 # 2-minute pause until next time.
|
self.LASTTRY = time.time() + 120 # 2-minute pause until next time.
|
||||||
else:
|
else:
|
||||||
logging.error("[Fix_Services] wifi.recon did not start up")
|
logging.error("[Fix_Services] wifi.recon did not start up")
|
||||||
@ -368,13 +375,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.info("[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
|
||||||
|
@ -14,19 +14,15 @@ import zipfile
|
|||||||
|
|
||||||
class GdriveSync(plugins.Plugin):
|
class GdriveSync(plugins.Plugin):
|
||||||
__author__ = '@jayofelony'
|
__author__ = '@jayofelony'
|
||||||
__version__ = '1.0'
|
__version__ = '1.2'
|
||||||
__license__ = 'GPL3'
|
__license__ = 'GPL3'
|
||||||
__description__ = 'A plugin to backup various pwnagotchi files and folders to Google Drive. Once every hour from loading plugin.'
|
__description__ = 'A plugin to backup various pwnagotchi files and folders to Google Drive. Once every hour from loading plugin.'
|
||||||
__dependencies__ = {
|
|
||||||
'pip': ['pydrive2']
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.options = dict()
|
self.options = dict()
|
||||||
self.lock = Lock()
|
self.lock = Lock()
|
||||||
self.internet = False
|
self.internet = False
|
||||||
self.ready = False
|
self.ready = False
|
||||||
self.drive = None
|
|
||||||
self.status = StatusFile('/root/.gdrive-backup')
|
self.status = StatusFile('/root/.gdrive-backup')
|
||||||
self.backup = True
|
self.backup = True
|
||||||
self.backupfiles = [
|
self.backupfiles = [
|
||||||
@ -38,12 +34,11 @@ class GdriveSync(plugins.Plugin):
|
|||||||
'/etc/pwnagotchi'
|
'/etc/pwnagotchi'
|
||||||
]
|
]
|
||||||
|
|
||||||
def on_loaded(self, agent):
|
def on_loaded(self):
|
||||||
"""
|
"""
|
||||||
Called when the plugin is loaded
|
Called when the plugin is loaded
|
||||||
"""
|
"""
|
||||||
# client_secrets.json needs to be not empty
|
# client_secrets.json needs to be not empty
|
||||||
display = agent.view()
|
|
||||||
if os.stat("/root/client_secrets.json").st_size == 0:
|
if os.stat("/root/client_secrets.json").st_size == 0:
|
||||||
logging.error("[gDriveSync] /root/client_secrets.json is empty. Please RTFM!")
|
logging.error("[gDriveSync] /root/client_secrets.json is empty. Please RTFM!")
|
||||||
return
|
return
|
||||||
@ -78,25 +73,24 @@ class GdriveSync(plugins.Plugin):
|
|||||||
# logging.warning(f"[gDriveSync] No files found in the folder with ID {root_file_list} and {pwnagotchi_file_list}")
|
# logging.warning(f"[gDriveSync] No files found in the folder with ID {root_file_list} and {pwnagotchi_file_list}")
|
||||||
if self.options['backupfiles'] is not None:
|
if self.options['backupfiles'] is not None:
|
||||||
self.backupfiles = self.backupfiles + self.options['backupfiles']
|
self.backupfiles = self.backupfiles + self.options['backupfiles']
|
||||||
self.backup_files(self.backupfiles, '/backup')
|
self.backup_files(self.backupfiles, '/home/pi/backup')
|
||||||
|
|
||||||
# Create a zip archive of the /backup folder
|
# Create a zip archive of the /backup folder
|
||||||
zip_file_path = os.path.join('/home/pi', 'backup.zip')
|
zip_file_path = os.path.join('/home/pi', 'backup.zip')
|
||||||
with zipfile.ZipFile(zip_file_path, 'w') as zip_ref:
|
with zipfile.ZipFile(zip_file_path, 'w') as zip_ref:
|
||||||
for root, dirs, files in os.walk('/backup'):
|
for root, dirs, files in os.walk('/home/pi/backup'):
|
||||||
for file in files:
|
for file in files:
|
||||||
file_path = os.path.join(root, file)
|
file_path = os.path.join(root, file)
|
||||||
arcname = os.path.relpath(file_path, '/backup')
|
arcname = os.path.relpath(file_path, '/home/pi/backup')
|
||||||
zip_ref.write(file_path, arcname=arcname)
|
zip_ref.write(file_path, arcname=arcname)
|
||||||
|
|
||||||
# Upload the zip archive to Google Drive
|
# Upload the zip archive to Google Drive
|
||||||
self.upload_to_gdrive(zip_file_path, self.get_folder_id_by_name(self.drive, self.options['backup_folder']))
|
self.upload_to_gdrive(zip_file_path, self.get_folder_id_by_name(self.drive, self.options['backup_folder']))
|
||||||
display.on_uploading("Google Drive")
|
|
||||||
self.backup = True
|
self.backup = True
|
||||||
self.status.update()
|
self.status.update()
|
||||||
|
|
||||||
# Specify the local backup path
|
# Specify the local backup path
|
||||||
local_backup_path = '/'
|
local_backup_path = '/home/pi/'
|
||||||
|
|
||||||
# Download the zip archive from Google Drive
|
# Download the zip archive from Google Drive
|
||||||
zip_file_id = self.get_latest_backup_file_id(self.options['backup_folder'])
|
zip_file_id = self.get_latest_backup_file_id(self.options['backup_folder'])
|
||||||
@ -104,7 +98,6 @@ class GdriveSync(plugins.Plugin):
|
|||||||
zip_file = self.drive.CreateFile({'id': zip_file_id})
|
zip_file = self.drive.CreateFile({'id': zip_file_id})
|
||||||
zip_file.GetContentFile(os.path.join(local_backup_path, 'backup.zip'))
|
zip_file.GetContentFile(os.path.join(local_backup_path, 'backup.zip'))
|
||||||
|
|
||||||
display.on_downloading("Google Drive")
|
|
||||||
logging.info("[gDriveSync] Downloaded backup.zip from Google Drive")
|
logging.info("[gDriveSync] Downloaded backup.zip from Google Drive")
|
||||||
|
|
||||||
# Extract the zip archive to the root directory
|
# Extract the zip archive to the root directory
|
||||||
@ -112,9 +105,12 @@ class GdriveSync(plugins.Plugin):
|
|||||||
zip_ref.extractall('/')
|
zip_ref.extractall('/')
|
||||||
|
|
||||||
self.status.update()
|
self.status.update()
|
||||||
os.remove("/backup")
|
shutil.rmtree("/home/pi/backup")
|
||||||
# Reboot so we can start opwngrid with the backup id
|
os.remove("/home/pi/backup.zip")
|
||||||
pwnagotchi.reboot()
|
self.ready = True
|
||||||
|
logging.info("[gdrivesync] loaded")
|
||||||
|
# Restart so we can start opwngrid with the backup id
|
||||||
|
pwnagotchi.restart("AUTO")
|
||||||
|
|
||||||
# all set, gdriveSync is ready to run
|
# all set, gdriveSync is ready to run
|
||||||
self.ready = True
|
self.ready = True
|
||||||
@ -186,15 +182,15 @@ class GdriveSync(plugins.Plugin):
|
|||||||
logging.info("[gdrivesync] new handshake captured, backing up to gdrive")
|
logging.info("[gdrivesync] new handshake captured, backing up to gdrive")
|
||||||
if self.options['backupfiles'] is not None:
|
if self.options['backupfiles'] is not None:
|
||||||
self.backupfiles = self.backupfiles + self.options['backupfiles']
|
self.backupfiles = self.backupfiles + self.options['backupfiles']
|
||||||
self.backup_files(self.backupfiles, '/backup')
|
self.backup_files(self.backupfiles, '/home/pi/backup')
|
||||||
|
|
||||||
# Create a zip archive of the /backup folder
|
# Create a zip archive of the /backup folder
|
||||||
zip_file_path = os.path.join('/home/pi', 'backup.zip')
|
zip_file_path = os.path.join('/home/pi', 'backup.zip')
|
||||||
with zipfile.ZipFile(zip_file_path, 'w') as zip_ref:
|
with zipfile.ZipFile(zip_file_path, 'w') as zip_ref:
|
||||||
for root, dirs, files in os.walk('/backup'):
|
for root, dirs, files in os.walk('/home/pi/backup'):
|
||||||
for file in files:
|
for file in files:
|
||||||
file_path = os.path.join(root, file)
|
file_path = os.path.join(root, file)
|
||||||
arcname = os.path.relpath(file_path, '/backup')
|
arcname = os.path.relpath(file_path, '/home/pi/backup')
|
||||||
zip_ref.write(file_path, arcname=arcname)
|
zip_ref.write(file_path, arcname=arcname)
|
||||||
|
|
||||||
# Upload the zip archive to Google Drive
|
# Upload the zip archive to Google Drive
|
||||||
@ -203,7 +199,7 @@ class GdriveSync(plugins.Plugin):
|
|||||||
|
|
||||||
# Cleanup the local zip file
|
# Cleanup the local zip file
|
||||||
os.remove(zip_file_path)
|
os.remove(zip_file_path)
|
||||||
os.remove("/backup")
|
shutil.rmtree("/home/pi/backup")
|
||||||
self.status.update()
|
self.status.update()
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
display.update(force=True, new_data={'Backing up to gdrive ...'})
|
display.update(force=True, new_data={'Backing up to gdrive ...'})
|
||||||
|
@ -14,8 +14,9 @@ class GPIOButtons(plugins.Plugin):
|
|||||||
self.running = False
|
self.running = False
|
||||||
self.ports = {}
|
self.ports = {}
|
||||||
self.commands = None
|
self.commands = None
|
||||||
|
self.options = dict()
|
||||||
|
|
||||||
def runCommand(self, channel):
|
def runcommand(self, channel):
|
||||||
command = self.ports[channel]
|
command = self.ports[channel]
|
||||||
logging.info(f"Button Pressed! Running command: {command}")
|
logging.info(f"Button Pressed! Running command: {command}")
|
||||||
process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None,
|
process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None,
|
||||||
@ -35,8 +36,8 @@ class GPIOButtons(plugins.Plugin):
|
|||||||
gpio = int(gpio)
|
gpio = int(gpio)
|
||||||
self.ports[gpio] = command
|
self.ports[gpio] = command
|
||||||
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
|
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
|
||||||
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runCommand, bouncetime=600)
|
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runcommand, bouncetime=600)
|
||||||
#set pimoroni display hat mini LED off/dim
|
# set pimoroni display hat mini LED off/dim
|
||||||
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
||||||
GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
||||||
GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
||||||
|
@ -20,6 +20,7 @@ class GPS(plugins.Plugin):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.running = False
|
self.running = False
|
||||||
self.coordinates = None
|
self.coordinates = None
|
||||||
|
self.options = dict()
|
||||||
|
|
||||||
def on_loaded(self):
|
def on_loaded(self):
|
||||||
logging.info(f"gps plugin loaded for {self.options['device']}")
|
logging.info(f"gps plugin loaded for {self.options['device']}")
|
||||||
@ -97,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
|
||||||
@ -48,7 +47,7 @@ class Grid(plugins.Plugin):
|
|||||||
__version__ = '1.0.1'
|
__version__ = '1.0.1'
|
||||||
__license__ = 'GPL3'
|
__license__ = 'GPL3'
|
||||||
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
|
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
|
||||||
'networks to api.pwnagotchi.ai '
|
'networks to opwngrid.xyz '
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.options = dict()
|
self.options = dict()
|
||||||
@ -58,8 +57,9 @@ class Grid(plugins.Plugin):
|
|||||||
self.total_messages = 0
|
self.total_messages = 0
|
||||||
self.lock = Lock()
|
self.lock = Lock()
|
||||||
|
|
||||||
def is_excluded(self, what):
|
def is_excluded(self, what, agent):
|
||||||
for skip in self.options['exclude']:
|
config = agent.config()
|
||||||
|
for skip in config['main']['whitelist']:
|
||||||
skip = skip.lower()
|
skip = skip.lower()
|
||||||
what = what.lower()
|
what = what.lower()
|
||||||
if skip in what or skip.replace(':', '') in what:
|
if skip in what or skip.replace(':', '') in what:
|
||||||
@ -86,9 +86,10 @@ 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()
|
||||||
|
|
||||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
pcap_files = glob.glob(os.path.join(config['bettercap']['handshakes'], "*.pcap"))
|
||||||
num_networks = len(pcap_files)
|
num_networks = len(pcap_files)
|
||||||
reported = self.report.data_field_or('reported', default=[])
|
reported = self.report.data_field_or('reported', default=[])
|
||||||
num_reported = len(reported)
|
num_reported = len(reported)
|
||||||
@ -98,19 +99,19 @@ class Grid(plugins.Plugin):
|
|||||||
if self.options['report']:
|
if self.options['report']:
|
||||||
logging.info("grid: %d new networks to report" % num_new)
|
logging.info("grid: %d new networks to report" % num_new)
|
||||||
logging.debug("self.options: %s" % self.options)
|
logging.debug("self.options: %s" % self.options)
|
||||||
logging.debug(" exclude: %s" % self.options['exclude'])
|
logging.debug(" exclude: %s" % config['main']['whitelist'])
|
||||||
|
|
||||||
for pcap_file in pcap_files:
|
for pcap_file in pcap_files:
|
||||||
net_id = os.path.basename(pcap_file).replace('.pcap', '')
|
net_id = os.path.basename(pcap_file).replace('.pcap', '')
|
||||||
if net_id not in reported:
|
if net_id not in reported:
|
||||||
if self.is_excluded(net_id):
|
if self.is_excluded(net_id, agent):
|
||||||
logging.debug("skipping %s due to exclusion filter" % pcap_file)
|
logging.debug("skipping %s due to exclusion filter" % pcap_file)
|
||||||
self.set_reported(reported, net_id)
|
self.set_reported(reported, net_id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
essid, bssid = parse_pcap(pcap_file)
|
essid, bssid = parse_pcap(pcap_file)
|
||||||
if bssid:
|
if bssid:
|
||||||
if self.is_excluded(essid) or self.is_excluded(bssid):
|
if self.is_excluded(essid, agent) or self.is_excluded(bssid, agent):
|
||||||
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
|
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
|
||||||
self.set_reported(reported, net_id)
|
self.set_reported(reported, net_id)
|
||||||
else:
|
else:
|
||||||
|
@ -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"
|
||||||
|
@ -130,7 +130,7 @@ class MemTemp(plugins.Plugin):
|
|||||||
except Exception:
|
except Exception:
|
||||||
# Set default position based on screen type
|
# Set default position based on screen type
|
||||||
if ui.is_waveshare_v2():
|
if ui.is_waveshare_v2():
|
||||||
h_pos = (178, 84)
|
h_pos = (175, 84)
|
||||||
v_pos = (197, 74)
|
v_pos = (197, 74)
|
||||||
elif ui.is_waveshare_v1():
|
elif ui.is_waveshare_v1():
|
||||||
h_pos = (170, 80)
|
h_pos = (170, 80)
|
||||||
@ -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)
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
import logging
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import threading
|
|
||||||
import requests
|
|
||||||
import time
|
|
||||||
import pwnagotchi.plugins as plugins
|
|
||||||
from pwnagotchi.utils import StatusFile
|
|
||||||
|
|
||||||
|
|
||||||
class NetPos(plugins.Plugin):
|
|
||||||
__author__ = 'zenzen san'
|
|
||||||
__version__ = '2.0.3'
|
|
||||||
__license__ = 'GPL3'
|
|
||||||
__description__ = """Saves a json file with the access points with more signal
|
|
||||||
whenever a handshake is captured.
|
|
||||||
When internet is available the files are converted in geo locations
|
|
||||||
using Mozilla LocationService """
|
|
||||||
|
|
||||||
API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.report = StatusFile('/root/.net_pos_saved', data_format='json')
|
|
||||||
self.skip = list()
|
|
||||||
self.ready = False
|
|
||||||
self.lock = threading.Lock()
|
|
||||||
|
|
||||||
def on_loaded(self):
|
|
||||||
if 'api_key' not in self.options or ('api_key' in self.options and not self.options['api_key']):
|
|
||||||
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
|
|
||||||
return
|
|
||||||
if 'api_url' in self.options:
|
|
||||||
self.API_URL = self.options['api_url']
|
|
||||||
self.ready = True
|
|
||||||
logging.info("net-pos plugin loaded.")
|
|
||||||
logging.debug(f"net-pos: use api_url: {self.API_URL}")
|
|
||||||
|
|
||||||
def _append_saved(self, path):
|
|
||||||
to_save = list()
|
|
||||||
if isinstance(path, str):
|
|
||||||
to_save.append(path)
|
|
||||||
elif isinstance(path, list):
|
|
||||||
to_save += path
|
|
||||||
else:
|
|
||||||
raise TypeError("Expected list or str, got %s" % type(path))
|
|
||||||
|
|
||||||
with open('/root/.net_pos_saved', 'a') as saved_file:
|
|
||||||
for x in to_save:
|
|
||||||
saved_file.write(x + "\n")
|
|
||||||
|
|
||||||
def on_internet_available(self, agent):
|
|
||||||
if self.lock.locked():
|
|
||||||
return
|
|
||||||
with self.lock:
|
|
||||||
if self.ready:
|
|
||||||
config = agent.config()
|
|
||||||
display = agent.view()
|
|
||||||
reported = self.report.data_field_or('reported', default=list())
|
|
||||||
handshake_dir = config['bettercap']['handshakes']
|
|
||||||
|
|
||||||
all_files = os.listdir(handshake_dir)
|
|
||||||
all_np_files = [os.path.join(handshake_dir, filename)
|
|
||||||
for filename in all_files
|
|
||||||
if filename.endswith('.net-pos.json')]
|
|
||||||
new_np_files = set(all_np_files) - set(reported) - set(self.skip)
|
|
||||||
|
|
||||||
if new_np_files:
|
|
||||||
logging.debug("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files))
|
|
||||||
display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...")
|
|
||||||
display.update(force=True)
|
|
||||||
for idx, np_file in enumerate(new_np_files):
|
|
||||||
|
|
||||||
geo_file = np_file.replace('.net-pos.json', '.geo.json')
|
|
||||||
if os.path.exists(geo_file):
|
|
||||||
# got already the position
|
|
||||||
reported.append(np_file)
|
|
||||||
self.report.update(data={'reported': reported})
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
geo_data = self._get_geo_data(np_file) # returns json obj
|
|
||||||
except requests.exceptions.RequestException as req_e:
|
|
||||||
logging.error("NET-POS: %s - RequestException: %s", np_file, req_e)
|
|
||||||
self.skip += np_file
|
|
||||||
continue
|
|
||||||
except json.JSONDecodeError as js_e:
|
|
||||||
logging.error("NET-POS: %s - JSONDecodeError: %s, removing it...", np_file, js_e)
|
|
||||||
os.remove(np_file)
|
|
||||||
continue
|
|
||||||
except OSError as os_e:
|
|
||||||
logging.error("NET-POS: %s - OSError: %s", np_file, os_e)
|
|
||||||
self.skip += np_file
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open(geo_file, 'w+t') as sf:
|
|
||||||
json.dump(geo_data, sf)
|
|
||||||
|
|
||||||
reported.append(np_file)
|
|
||||||
self.report.update(data={'reported': reported})
|
|
||||||
|
|
||||||
display.set('status', f"Fetching positions ({idx + 1}/{len(new_np_files)})")
|
|
||||||
display.update(force=True)
|
|
||||||
|
|
||||||
def on_handshake(self, agent, filename, access_point, client_station):
|
|
||||||
netpos = self._get_netpos(agent)
|
|
||||||
if not netpos['wifiAccessPoints']:
|
|
||||||
return
|
|
||||||
|
|
||||||
netpos["ts"] = int("%.0f" % time.time())
|
|
||||||
netpos_filename = filename.replace('.pcap', '.net-pos.json')
|
|
||||||
logging.debug("NET-POS: Saving net-location to %s", netpos_filename)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(netpos_filename, 'w+t') as net_pos_file:
|
|
||||||
json.dump(netpos, net_pos_file)
|
|
||||||
except OSError as os_e:
|
|
||||||
logging.error("NET-POS: %s", os_e)
|
|
||||||
|
|
||||||
def _get_netpos(self, agent):
|
|
||||||
aps = agent.get_access_points()
|
|
||||||
netpos = dict()
|
|
||||||
netpos['wifiAccessPoints'] = list()
|
|
||||||
# 6 seems a good number to save a wifi networks location
|
|
||||||
for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]:
|
|
||||||
netpos['wifiAccessPoints'].append({'macAddress': access_point['mac'],
|
|
||||||
'signalStrength': access_point['rssi']})
|
|
||||||
return netpos
|
|
||||||
|
|
||||||
def _get_geo_data(self, path, timeout=30):
|
|
||||||
geourl = self.API_URL.format(api=self.options['api_key'])
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(path, "r") as json_file:
|
|
||||||
data = json.load(json_file)
|
|
||||||
except json.JSONDecodeError as js_e:
|
|
||||||
raise js_e
|
|
||||||
except OSError as os_e:
|
|
||||||
raise os_e
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = requests.post(geourl,
|
|
||||||
json=data,
|
|
||||||
timeout=timeout)
|
|
||||||
return_geo = result.json()
|
|
||||||
if data["ts"]:
|
|
||||||
return_geo["ts"] = data["ts"]
|
|
||||||
return return_geo
|
|
||||||
except requests.exceptions.RequestException as req_e:
|
|
||||||
raise req_e
|
|
@ -25,6 +25,7 @@ class OnlineHashCrack(plugins.Plugin):
|
|||||||
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
|
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
|
||||||
self.skip = list()
|
self.skip = list()
|
||||||
self.lock = Lock()
|
self.lock = Lock()
|
||||||
|
self.options = dict()
|
||||||
|
|
||||||
def on_loaded(self):
|
def on_loaded(self):
|
||||||
"""
|
"""
|
||||||
@ -34,13 +35,9 @@ class OnlineHashCrack(plugins.Plugin):
|
|||||||
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
|
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'whitelist' not in self.options:
|
|
||||||
self.options['whitelist'] = list()
|
|
||||||
|
|
||||||
self.ready = True
|
self.ready = True
|
||||||
logging.info("OHC: OnlineHashCrack plugin loaded.")
|
logging.info("OHC: OnlineHashCrack plugin loaded.")
|
||||||
|
|
||||||
|
|
||||||
def _upload_to_ohc(self, path, timeout=30):
|
def _upload_to_ohc(self, path, timeout=30):
|
||||||
"""
|
"""
|
||||||
Uploads the file to onlinehashcrack.com
|
Uploads the file to onlinehashcrack.com
|
||||||
@ -78,7 +75,6 @@ class OnlineHashCrack(plugins.Plugin):
|
|||||||
except OSError as os_e:
|
except OSError as os_e:
|
||||||
raise os_e
|
raise os_e
|
||||||
|
|
||||||
|
|
||||||
def on_webhook(self, path, request):
|
def on_webhook(self, path, request):
|
||||||
import requests
|
import requests
|
||||||
from flask import redirect
|
from flask import redirect
|
||||||
@ -87,7 +83,6 @@ class OnlineHashCrack(plugins.Plugin):
|
|||||||
r = s.post('https://www.onlinehashcrack.com/dashboard', data={'emailTasks': self.options['email'], 'submit': ''})
|
r = s.post('https://www.onlinehashcrack.com/dashboard', data={'emailTasks': self.options['email'], 'submit': ''})
|
||||||
return redirect(r.url, code=302)
|
return redirect(r.url, code=302)
|
||||||
|
|
||||||
|
|
||||||
def on_internet_available(self, agent):
|
def on_internet_available(self, agent):
|
||||||
"""
|
"""
|
||||||
Called in manual mode when there's internet connectivity
|
Called in manual mode when there's internet connectivity
|
||||||
@ -105,7 +100,7 @@ class OnlineHashCrack(plugins.Plugin):
|
|||||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
|
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
|
||||||
filename.endswith('.pcap')]
|
filename.endswith('.pcap')]
|
||||||
# pull out whitelisted APs
|
# pull out whitelisted APs
|
||||||
handshake_paths = remove_whitelisted(handshake_paths, self.options['whitelist'])
|
handshake_paths = remove_whitelisted(handshake_paths, config['main']['whitelist'])
|
||||||
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
|
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
|
||||||
if handshake_new:
|
if handshake_new:
|
||||||
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com")
|
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com")
|
||||||
@ -147,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'])
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
import logging
|
|
||||||
import requests
|
|
||||||
import pwnagotchi.plugins as plugins
|
|
||||||
|
|
||||||
'''
|
|
||||||
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemik and edited by shaynemk
|
|
||||||
GUIDE HERE: https://community.pwnagotchi.ai/t/setting-up-paw-gps-on-android
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class PawGPS(plugins.Plugin):
|
|
||||||
__author__ = 'leont'
|
|
||||||
__version__ = '1.0.1'
|
|
||||||
__name__ = 'pawgps'
|
|
||||||
__license__ = 'GPL3'
|
|
||||||
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android.'
|
|
||||||
|
|
||||||
def on_loaded(self):
|
|
||||||
logging.info("[paw-gps] plugin loaded")
|
|
||||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None) or (len('ip' in self.options and self.options['ip']) is 0):
|
|
||||||
logging.info("[paw-gps] no IP Address defined in the config file, will uses paw server default (192.168.44.1:8080)")
|
|
||||||
|
|
||||||
def on_handshake(self, agent, filename, access_point, client_station):
|
|
||||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None or (len('ip' in self.options and self.options['ip']) is 0)):
|
|
||||||
ip = "192.168.44.1:8080"
|
|
||||||
else:
|
|
||||||
ip = self.options['ip']
|
|
||||||
|
|
||||||
try:
|
|
||||||
gps = requests.get('http://' + ip + '/gps.xhtml')
|
|
||||||
try:
|
|
||||||
gps_filename = filename.replace('.pcap', '.paw-gps.json')
|
|
||||||
logging.info("[paw-gps] saving GPS data to %s" % (gps_filename))
|
|
||||||
with open(gps_filename, 'w+t') as f:
|
|
||||||
f.write(gps.text)
|
|
||||||
except Exception as error:
|
|
||||||
logging.error(f"[paw-gps] encountered error while saving gps data: {error}")
|
|
||||||
except Exception as error:
|
|
||||||
logging.error(f"[paw-gps] encountered error while getting gps data: {error}")
|
|
105
pwnagotchi/plugins/default/pisugar3.py
Normal file
105
pwnagotchi/plugins/default/pisugar3.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# Based on UPS Lite v1.1 from https://github.com/xenDE
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from pwnagotchi.ui.components import LabeledValue
|
||||||
|
from pwnagotchi.ui.view import BLACK
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
import pwnagotchi.plugins as plugins
|
||||||
|
import pwnagotchi
|
||||||
|
|
||||||
|
|
||||||
|
class UPS:
|
||||||
|
def __init__(self):
|
||||||
|
# only import when the module is loaded and enabled
|
||||||
|
import smbus
|
||||||
|
# 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
|
||||||
|
self._bus = smbus.SMBus(1)
|
||||||
|
|
||||||
|
def voltage(self):
|
||||||
|
try:
|
||||||
|
low = self._bus.read_byte_data(0x57, 0x23)
|
||||||
|
high = self._bus.read_byte_data(0x57, 0x22)
|
||||||
|
v = (((high << 8) + low) / 1000)
|
||||||
|
return v
|
||||||
|
except:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def capacity(self):
|
||||||
|
battery_level = 0
|
||||||
|
# battery_v = self.voltage()
|
||||||
|
try:
|
||||||
|
battery_level = self._bus.read_byte_data(0x57, 0x2a)
|
||||||
|
return battery_level
|
||||||
|
except:
|
||||||
|
return battery_level
|
||||||
|
|
||||||
|
def status(self):
|
||||||
|
stat02 = self._bus.read_byte_data(0x57, 0x02)
|
||||||
|
stat03 = self._bus.read_byte_data(0x57, 0x03)
|
||||||
|
stat04 = self._bus.read_byte_data(0x57, 0x04)
|
||||||
|
return stat02, stat03, stat04
|
||||||
|
|
||||||
|
|
||||||
|
class PiSugar3(plugins.Plugin):
|
||||||
|
__author__ = 'taiyonemo@protonmail.com'
|
||||||
|
__editor__ = 'jayofelony'
|
||||||
|
__version__ = '1.0.1'
|
||||||
|
__license__ = 'GPL3'
|
||||||
|
__description__ = 'A plugin that will add a percentage indicator for the PiSugar 3'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.ups = None
|
||||||
|
self.lasttemp = 69
|
||||||
|
self.drot = 0 # display rotation
|
||||||
|
self.nextDChg = 0 # last time display changed, rotate on updates after 5 seconds
|
||||||
|
self.options = dict()
|
||||||
|
|
||||||
|
def on_loaded(self):
|
||||||
|
self.ups = UPS()
|
||||||
|
logging.info("[PiSugar3] plugin loaded.")
|
||||||
|
|
||||||
|
def on_ui_setup(self, ui):
|
||||||
|
try:
|
||||||
|
ui.add_element('bat', LabeledValue(color=BLACK, label='BAT', value='0%', position=(ui.width() / 2 + 10, 0),
|
||||||
|
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||||
|
except Exception as err:
|
||||||
|
logging.warning("[PiSugar3] setup err: %s" % repr(err))
|
||||||
|
|
||||||
|
def on_unload(self, ui):
|
||||||
|
try:
|
||||||
|
with ui._lock:
|
||||||
|
ui.remove_element('bat')
|
||||||
|
except Exception as err:
|
||||||
|
logging.warning("[PiSugar3] unload err: %s" % repr(err))
|
||||||
|
|
||||||
|
def on_ui_update(self, ui):
|
||||||
|
capacity = self.ups.capacity()
|
||||||
|
voltage = self.ups.voltage()
|
||||||
|
stats = self.ups.status()
|
||||||
|
temp = stats[2] - 40
|
||||||
|
if temp != self.lasttemp:
|
||||||
|
logging.debug("[PiSugar3] (chg %X, info %X, temp %d)" % (stats[0], stats[1], temp))
|
||||||
|
self.lasttemp = temp
|
||||||
|
|
||||||
|
if stats[0] & 0x80: # charging, or has power connected
|
||||||
|
ui._state._state['bat'].label = "CHG"
|
||||||
|
else:
|
||||||
|
ui._state._state['bat'].label = "BAT"
|
||||||
|
|
||||||
|
if time.time() > self.nextDChg:
|
||||||
|
self.drot = (self.drot + 1) % 3
|
||||||
|
self.nextDChg = time.time() + 5
|
||||||
|
|
||||||
|
if self.drot == 0: # show battery voltage
|
||||||
|
ui.set('bat', "%2.2fv" % voltage)
|
||||||
|
elif self.drot == 1:
|
||||||
|
ui.set('bat', "%2i%%" % capacity)
|
||||||
|
else:
|
||||||
|
ui.set('bat', "%2i\xb0" % temp)
|
||||||
|
|
||||||
|
if capacity <= self.options['shutdown']:
|
||||||
|
logging.info('[PiSugar3] Empty battery (<= %s%%): shutting down' % self.options['shutdown'])
|
||||||
|
ui.update(force=True, new_data={'status': 'Battery exhausted, bye ...'})
|
||||||
|
pwnagotchi.shutdown()
|
@ -18,12 +18,14 @@ def systemd_dropin(name, content):
|
|||||||
|
|
||||||
systemctl("daemon-reload")
|
systemctl("daemon-reload")
|
||||||
|
|
||||||
|
|
||||||
def systemctl(command, unit=None):
|
def systemctl(command, unit=None):
|
||||||
if unit:
|
if unit:
|
||||||
os.system("/bin/systemctl %s %s" % (command, unit))
|
os.system("/bin/systemctl %s %s" % (command, unit))
|
||||||
else:
|
else:
|
||||||
os.system("/bin/systemctl %s" % command)
|
os.system("/bin/systemctl %s" % command)
|
||||||
|
|
||||||
|
|
||||||
def run_task(name, options):
|
def run_task(name, options):
|
||||||
task_service_name = "switcher-%s-task.service" % name
|
task_service_name = "switcher-%s-task.service" % name
|
||||||
# save all the commands to a shell script
|
# save all the commands to a shell script
|
||||||
@ -57,7 +59,7 @@ def run_task(name, options):
|
|||||||
""" % (name, task_service_name, name))
|
""" % (name, task_service_name, name))
|
||||||
|
|
||||||
if 'reboot' in options and options['reboot']:
|
if 'reboot' in options and options['reboot']:
|
||||||
# create a indication file!
|
# create an indication file!
|
||||||
# if this file is set, we want the switcher-tasks to run
|
# if this file is set, we want the switcher-tasks to run
|
||||||
open('/root/.switcher', 'a').close()
|
open('/root/.switcher', 'a').close()
|
||||||
|
|
||||||
@ -98,6 +100,7 @@ def run_task(name, options):
|
|||||||
systemctl("daemon-reload")
|
systemctl("daemon-reload")
|
||||||
systemctl("start", task_service_name)
|
systemctl("start", task_service_name)
|
||||||
|
|
||||||
|
|
||||||
class Switcher(plugins.Plugin):
|
class Switcher(plugins.Plugin):
|
||||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||||
__version__ = '0.0.1'
|
__version__ = '0.0.1'
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
# Based on UPS Lite v1.1 from https://github.com/xenDE
|
# Based on UPS Lite v1.1 from https://github.com/xenDE
|
||||||
|
# Made specifically to address the problems caused by the hardware changes in 1.3. Oh yeah I also removed the auto-shutdown feature because it's kind of broken.
|
||||||
#
|
#
|
||||||
# functions for get UPS status - needs enable "i2c" in raspi-config
|
# To setup, see page six of this manual to see how to enable i2c:
|
||||||
|
# https://github.com/linshuqin329/UPS-Lite/blob/master/UPS-Lite_V1.3_CW2015/Instructions%20for%20UPS-Lite%20V1.3.pdf
|
||||||
#
|
#
|
||||||
# https://github.com/linshuqin329/UPS-Lite
|
# Follow page seven, install the dependencies (python-smbus) and copy this script over for later use:
|
||||||
|
# https://github.com/linshuqin329/UPS-Lite/blob/master/UPS-Lite_V1.3_CW2015/UPS_Lite_V1.3_CW2015.py
|
||||||
#
|
#
|
||||||
# For Raspberry Pi Zero Ups Power Expansion Board with Integrated Serial Port S3U4
|
# Now, install this plugin by copying this to the 'available-plugins' folder in your pwnagotchi, install and enable the plugin with the commands:
|
||||||
# https://www.ebay.de/itm/For-Raspberry-Pi-Zero-Ups-Power-Expansion-Board-with-Integrated-Serial-Port-S3U4/323873804310
|
# sudo pwnagotchi plugins install upslite_plugin_1_3
|
||||||
# https://www.aliexpress.com/item/32888533624.html
|
# sudo pwnagotchi plugins enable upslite_plugin_1_3
|
||||||
#
|
#
|
||||||
# To display external power supply status you need to bridge the necessary pins on the UPS-Lite board. See instructions in the UPS-Lite repo.
|
# Now restart raspberry pi. Once back up ensure upslite_plugin_1_3 plugin is turned on in the WebUI. If there is still '0%' on your battery meter
|
||||||
|
# run the script we saved earlier and ensure that the pwnagotchi is plugged in both at the battery and the raspberry pi. The script should start trying to
|
||||||
|
# read the battery, and should be successful once there's a USB cable running power to the battery supply.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
@ -20,6 +26,11 @@ import pwnagotchi.ui.fonts as fonts
|
|||||||
from pwnagotchi.ui.components import LabeledValue
|
from pwnagotchi.ui.components import LabeledValue
|
||||||
from pwnagotchi.ui.view import BLACK
|
from pwnagotchi.ui.view import BLACK
|
||||||
|
|
||||||
|
CW2015_ADDRESS = 0X62
|
||||||
|
CW2015_REG_VCELL = 0X02
|
||||||
|
CW2015_REG_SOC = 0X04
|
||||||
|
CW2015_REG_MODE = 0X0A
|
||||||
|
|
||||||
|
|
||||||
# TODO: add enable switch in config.yml an cleanup all to the best place
|
# TODO: add enable switch in config.yml an cleanup all to the best place
|
||||||
class UPS:
|
class UPS:
|
||||||
@ -31,8 +42,7 @@ class UPS:
|
|||||||
|
|
||||||
def voltage(self):
|
def voltage(self):
|
||||||
try:
|
try:
|
||||||
address = 0x36
|
read = self._bus.read_word_data(CW2015_ADDRESS, CW2015_REG_VCELL)
|
||||||
read = self._bus.read_word_data(address, 2)
|
|
||||||
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
|
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
|
||||||
return swapped * 1.25 / 1000 / 16
|
return swapped * 1.25 / 1000 / 16
|
||||||
except:
|
except:
|
||||||
@ -41,7 +51,7 @@ class UPS:
|
|||||||
def capacity(self):
|
def capacity(self):
|
||||||
try:
|
try:
|
||||||
address = 0x36
|
address = 0x36
|
||||||
read = self._bus.read_word_data(address, 4)
|
read = self._bus.read_word_data(CW2015_ADDRESS, CW2015_REG_SOC)
|
||||||
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
|
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
|
||||||
return swapped / 256
|
return swapped / 256
|
||||||
except:
|
except:
|
||||||
@ -57,10 +67,10 @@ class UPS:
|
|||||||
|
|
||||||
|
|
||||||
class UPSLite(plugins.Plugin):
|
class UPSLite(plugins.Plugin):
|
||||||
__author__ = 'evilsocket@gmail.com'
|
__author__ = 'marbasec'
|
||||||
__version__ = '1.0.0'
|
__version__ = '1.3.0'
|
||||||
__license__ = 'GPL3'
|
__license__ = 'GPL3'
|
||||||
__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1'
|
__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.3'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ups = None
|
self.ups = None
|
||||||
@ -69,7 +79,7 @@ class UPSLite(plugins.Plugin):
|
|||||||
self.ups = UPS()
|
self.ups = UPS()
|
||||||
|
|
||||||
def on_ui_setup(self, ui):
|
def on_ui_setup(self, ui):
|
||||||
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 + 15, 0),
|
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%', position=(ui.width() / 2 + 15, 0),
|
||||||
label_font=fonts.Bold, text_font=fonts.Medium))
|
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||||
|
|
||||||
def on_unload(self, ui):
|
def on_unload(self, ui):
|
||||||
@ -80,7 +90,3 @@ class UPSLite(plugins.Plugin):
|
|||||||
capacity = self.ups.capacity()
|
capacity = self.ups.capacity()
|
||||||
charging = self.ups.charging()
|
charging = self.ups.charging()
|
||||||
ui.set('ups', "%2i%s" % (capacity, charging))
|
ui.set('ups', "%2i%s" % (capacity, charging))
|
||||||
if capacity <= self.options['shutdown']:
|
|
||||||
logging.info('[ups_lite] Empty battery (<= %s%%): shuting down' % self.options['shutdown'])
|
|
||||||
ui.update(force=True, new_data={'status': 'Battery exhausted, bye ...'})
|
|
||||||
pwnagotchi.shutdown()
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
import pwnagotchi.plugins as plugins
|
import pwnagotchi.plugins as plugins
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import datetime
|
|
||||||
from flask import Response
|
from flask import Response
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
@ -13,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
|
||||||
@ -22,6 +23,7 @@ from dateutil.parser import parse
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
class Webgpsmap(plugins.Plugin):
|
class Webgpsmap(plugins.Plugin):
|
||||||
__author__ = 'https://github.com/xenDE and https://github.com/dadav'
|
__author__ = 'https://github.com/xenDE and https://github.com/dadav'
|
||||||
__version__ = '1.4.0'
|
__version__ = '1.4.0'
|
||||||
@ -85,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'
|
||||||
@ -98,12 +101,13 @@ 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"
|
||||||
response_header_contenttype = 'text/html'
|
response_header_contenttype = 'text/html'
|
||||||
response_header_contentdisposition = 'attachment; filename=webgpsmap.html';
|
response_header_contentdisposition = 'attachment; filename=webgpsmap.html'
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logging.error(f"[webgpsmap] on_webhook offlinemap: error: {error}")
|
logging.error(f"[webgpsmap] on_webhook offlinemap: error: {error}")
|
||||||
return
|
return
|
||||||
@ -149,7 +153,6 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
def _get_pos_from_file(self, path):
|
def _get_pos_from_file(self, path):
|
||||||
return PositionFile(path)
|
return PositionFile(path)
|
||||||
|
|
||||||
|
|
||||||
def load_gps_from_dir(self, gpsdir, newest_only=False):
|
def load_gps_from_dir(self, gpsdir, newest_only=False):
|
||||||
"""
|
"""
|
||||||
Parses the gps-data from disk
|
Parses the gps-data from disk
|
||||||
@ -160,13 +163,10 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
|
|
||||||
logging.info(f"[webgpsmap] scanning {handshake_dir}")
|
logging.info(f"[webgpsmap] scanning {handshake_dir}")
|
||||||
|
|
||||||
|
|
||||||
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)
|
all_pcap_files = [os.path.join(handshake_dir, filename) for filename in all_files if
|
||||||
for filename in all_files
|
filename.endswith('.pcap')]
|
||||||
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"
|
||||||
@ -183,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:
|
||||||
@ -216,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,
|
||||||
@ -227,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"
|
||||||
@ -268,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
|
||||||
@ -285,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
|
||||||
@ -295,12 +288,11 @@ 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
|
||||||
|
|
||||||
|
|
||||||
def json(self):
|
def json(self):
|
||||||
"""
|
"""
|
||||||
returns the parsed json
|
returns the parsed json
|
||||||
@ -358,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):
|
||||||
@ -406,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']
|
||||||
|
@ -3,6 +3,7 @@ import logging
|
|||||||
import json
|
import json
|
||||||
import csv
|
import csv
|
||||||
import requests
|
import requests
|
||||||
|
import pwnagotchi
|
||||||
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -24,7 +25,11 @@ def _extract_gps_data(path):
|
|||||||
with open(path, 'r') as json_file:
|
with open(path, 'r') as json_file:
|
||||||
tempJson = json.load(json_file)
|
tempJson = json.load(json_file)
|
||||||
d = datetime.utcfromtimestamp(int(tempJson["ts"]))
|
d = datetime.utcfromtimestamp(int(tempJson["ts"]))
|
||||||
return {"Latitude": tempJson["location"]["lat"], "Longitude": tempJson["location"]["lng"], "Altitude": 10, "Updated": d.strftime('%Y-%m-%dT%H:%M:%S.%f')}
|
return {"Latitude": tempJson["location"]["lat"],
|
||||||
|
"Longitude": tempJson["location"]["lng"],
|
||||||
|
"Altitude": 10,
|
||||||
|
"Accuracy": tempJson["accuracy"],
|
||||||
|
"Updated": d.strftime('%Y-%m-%dT%H:%M:%S.%f')}
|
||||||
else:
|
else:
|
||||||
with open(path, 'r') as json_file:
|
with open(path, 'r') as json_file:
|
||||||
return json.load(json_file)
|
return json.load(json_file)
|
||||||
@ -38,7 +43,7 @@ def _format_auth(data):
|
|||||||
out = ""
|
out = ""
|
||||||
for auth in data:
|
for auth in data:
|
||||||
out = f"{out}[{auth}]"
|
out = f"{out}[{auth}]"
|
||||||
return out
|
return [f"{auth}" for auth in data]
|
||||||
|
|
||||||
|
|
||||||
def _transform_wigle_entry(gps_data, pcap_data, plugin_version):
|
def _transform_wigle_entry(gps_data, pcap_data, plugin_version):
|
||||||
@ -47,10 +52,10 @@ def _transform_wigle_entry(gps_data, pcap_data, plugin_version):
|
|||||||
"""
|
"""
|
||||||
dummy = StringIO()
|
dummy = StringIO()
|
||||||
# write kismet header
|
# write kismet header
|
||||||
|
dummy.write(f"WigleWifi-1.6,appRelease={plugin_version},model=pwnagotchi,release={__pwnagotchi_version__},"
|
||||||
|
f"device={pwnagotchi.name()},display=kismet,board=RaspberryPi,brand=pwnagotchi,star=Sol,body=3,subBody=0\n")
|
||||||
dummy.write(
|
dummy.write(
|
||||||
"WigleWifi-1.4,appRelease={},model=pwnagotchi,release={},device=pwnagotchi,display=kismet,board=kismet,brand=pwnagotchi\n".format(plugin_version, __pwnagotchi_version__))
|
"MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\n")
|
||||||
dummy.write(
|
|
||||||
"MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type")
|
|
||||||
|
|
||||||
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\")
|
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\")
|
||||||
writer.writerow([
|
writer.writerow([
|
||||||
@ -64,7 +69,7 @@ def _transform_wigle_entry(gps_data, pcap_data, plugin_version):
|
|||||||
gps_data['Latitude'],
|
gps_data['Latitude'],
|
||||||
gps_data['Longitude'],
|
gps_data['Longitude'],
|
||||||
gps_data['Altitude'],
|
gps_data['Altitude'],
|
||||||
0, # accuracy?
|
gps_data['Accuracy'],
|
||||||
'WIFI'])
|
'WIFI'])
|
||||||
return dummy.getvalue()
|
return dummy.getvalue()
|
||||||
|
|
||||||
@ -84,7 +89,7 @@ def _send_to_wigle(lines, api_key, donate=True, timeout=30):
|
|||||||
headers = {'Authorization': f"Basic {api_key}",
|
headers = {'Authorization': f"Basic {api_key}",
|
||||||
'Accept': 'application/json'}
|
'Accept': 'application/json'}
|
||||||
data = {'donate': 'on' if donate else 'false'}
|
data = {'donate': 'on' if donate else 'false'}
|
||||||
payload = {'file': dummy, 'type': 'text/csv'}
|
payload = {'file': (pwnagotchi.name() + ".csv", dummy, 'multipart/form-data', {'Expires': '0'})}
|
||||||
try:
|
try:
|
||||||
res = requests.post('https://api.wigle.net/api/v2/file/upload',
|
res = requests.post('https://api.wigle.net/api/v2/file/upload',
|
||||||
data=data,
|
data=data,
|
||||||
@ -99,34 +104,32 @@ def _send_to_wigle(lines, api_key, donate=True, timeout=30):
|
|||||||
|
|
||||||
|
|
||||||
class Wigle(plugins.Plugin):
|
class Wigle(plugins.Plugin):
|
||||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
__author__ = "Dadav and updated by Jayofelony"
|
||||||
__version__ = '2.0.0'
|
__version__ = "3.0.1"
|
||||||
__license__ = 'GPL3'
|
__license__ = "GPL3"
|
||||||
__description__ = 'This plugin automatically uploads collected wifis to wigle.net'
|
__description__ = "This plugin automatically uploads collected WiFi to wigle.net"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ready = False
|
self.ready = False
|
||||||
self.report = StatusFile('/root/.wigle_uploads', data_format='json')
|
self.report = StatusFile('/root/.wigle_uploads', data_format='json')
|
||||||
self.skip = list()
|
self.skip = list()
|
||||||
self.lock = Lock()
|
self.lock = Lock()
|
||||||
|
self.options = dict()
|
||||||
|
|
||||||
def on_loaded(self):
|
def on_loaded(self):
|
||||||
if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
|
if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
|
||||||
logging.debug("WIGLE: api_key isn't set. Can't upload to wigle.net")
|
logging.debug("WIGLE: api_key isn't set. Can't upload to wigle.net")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not 'whitelist' in self.options:
|
if 'donate' not in self.options:
|
||||||
self.options['whitelist'] = list()
|
self.options['donate'] = False
|
||||||
|
|
||||||
if not 'donate' in self.options:
|
|
||||||
self.options['donate'] = True
|
|
||||||
|
|
||||||
self.ready = True
|
self.ready = True
|
||||||
logging.info("WIGLE: ready")
|
logging.info("WIGLE: ready")
|
||||||
|
|
||||||
def on_internet_available(self, agent):
|
def on_internet_available(self, agent):
|
||||||
"""
|
"""
|
||||||
Called in manual mode when there's internet connectivity
|
Called when there's internet connectivity
|
||||||
"""
|
"""
|
||||||
if not self.ready or self.lock.locked():
|
if not self.ready or self.lock.locked():
|
||||||
return
|
return
|
||||||
@ -140,9 +143,9 @@ class Wigle(plugins.Plugin):
|
|||||||
all_files = os.listdir(handshake_dir)
|
all_files = os.listdir(handshake_dir)
|
||||||
all_gps_files = [os.path.join(handshake_dir, filename)
|
all_gps_files = [os.path.join(handshake_dir, filename)
|
||||||
for filename in all_files
|
for filename in all_files
|
||||||
if filename.endswith('.gps.json') or filename.endswith('.paw-gps.json') or filename.endswith('.geo.json')]
|
if filename.endswith('.gps.json') or filename.endswith('.geo.json')]
|
||||||
|
|
||||||
all_gps_files = remove_whitelisted(all_gps_files, self.options['whitelist'])
|
all_gps_files = remove_whitelisted(all_gps_files, config['main']['whitelist'])
|
||||||
new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
|
new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
|
||||||
if new_gps_files:
|
if new_gps_files:
|
||||||
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
|
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
|
||||||
@ -151,8 +154,6 @@ class Wigle(plugins.Plugin):
|
|||||||
for gps_file in new_gps_files:
|
for gps_file in new_gps_files:
|
||||||
if gps_file.endswith('.gps.json'):
|
if gps_file.endswith('.gps.json'):
|
||||||
pcap_filename = gps_file.replace('.gps.json', '.pcap')
|
pcap_filename = gps_file.replace('.gps.json', '.pcap')
|
||||||
if gps_file.endswith('.paw-gps.json'):
|
|
||||||
pcap_filename = gps_file.replace('.paw-gps.json', '.pcap')
|
|
||||||
if gps_file.endswith('.geo.json'):
|
if gps_file.endswith('.geo.json'):
|
||||||
pcap_filename = gps_file.replace('.geo.json', '.pcap')
|
pcap_filename = gps_file.replace('.geo.json', '.pcap')
|
||||||
if not os.path.exists(pcap_filename):
|
if not os.path.exists(pcap_filename):
|
||||||
|
@ -76,9 +76,6 @@ class WpaSec(plugins.Plugin):
|
|||||||
logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
|
logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'whitelist' not in self.options:
|
|
||||||
self.options['whitelist'] = list()
|
|
||||||
|
|
||||||
self.ready = True
|
self.ready = True
|
||||||
logging.info("WPA_SEC: plugin loaded")
|
logging.info("WPA_SEC: plugin loaded")
|
||||||
|
|
||||||
@ -101,9 +98,8 @@ class WpaSec(plugins.Plugin):
|
|||||||
reported = self.report.data_field_or('reported', default=list())
|
reported = self.report.data_field_or('reported', default=list())
|
||||||
handshake_dir = config['bettercap']['handshakes']
|
handshake_dir = config['bettercap']['handshakes']
|
||||||
handshake_filenames = os.listdir(handshake_dir)
|
handshake_filenames = os.listdir(handshake_dir)
|
||||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
|
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
|
||||||
filename.endswith('.pcap')]
|
handshake_paths = remove_whitelisted(handshake_paths, config['main']['whitelist'])
|
||||||
handshake_paths = remove_whitelisted(handshake_paths, self.options['whitelist'])
|
|
||||||
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
|
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
|
||||||
|
|
||||||
if handshake_new:
|
if handshake_new:
|
||||||
|
33
pwnagotchi/ui/colors.py
Normal file
33
pwnagotchi/ui/colors.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
LOOK_R = '( ⚆_⚆)'
|
||||||
|
LOOK_L = '(☉_☉ )'
|
||||||
|
LOOK_R_HAPPY = '( ◕‿◕)'
|
||||||
|
LOOK_L_HAPPY = '(◕‿◕ )'
|
||||||
|
SLEEP = '(⇀‿‿↼)'
|
||||||
|
SLEEP2 = '(≖‿‿≖)'
|
||||||
|
AWAKE = '(◕‿‿◕)'
|
||||||
|
BORED = '(-__-)'
|
||||||
|
INTENSE = '(°▃▃°)'
|
||||||
|
COOL = '(⌐■_■)'
|
||||||
|
HAPPY = '(•‿‿•)'
|
||||||
|
GRATEFUL = '(^‿‿^)'
|
||||||
|
EXCITED = '(ᵔ◡◡ᵔ)'
|
||||||
|
MOTIVATED = '(☼‿‿☼)'
|
||||||
|
DEMOTIVATED = '(≖__≖)'
|
||||||
|
SMART = '(✜‿‿✜)'
|
||||||
|
LONELY = '(ب__ب)'
|
||||||
|
SAD = '(╥☁╥ )'
|
||||||
|
ANGRY = "(-_-')"
|
||||||
|
FRIEND = '(♥‿‿♥)'
|
||||||
|
BROKEN = '(☓‿‿☓)'
|
||||||
|
DEBUG = '(#__#)'
|
||||||
|
UPLOAD = '(1__0)'
|
||||||
|
UPLOAD1 = '(1__1)'
|
||||||
|
UPLOAD2 = '(0__1)'
|
||||||
|
PNG = False
|
||||||
|
POSITION_X = 0
|
||||||
|
POSITION_Y = 40
|
||||||
|
|
||||||
|
|
||||||
|
def load_from_config(config):
|
||||||
|
for face_name, face_value in config.items():
|
||||||
|
globals()[face_name.upper()] = face_value
|
@ -1,4 +1,4 @@
|
|||||||
from PIL import Image
|
from PIL import Image, ImageOps
|
||||||
from textwrap import TextWrapper
|
from textwrap import TextWrapper
|
||||||
|
|
||||||
|
|
||||||
@ -10,13 +10,17 @@ class Widget(object):
|
|||||||
def draw(self, canvas, drawer):
|
def draw(self, canvas, drawer):
|
||||||
raise Exception("not implemented")
|
raise Exception("not implemented")
|
||||||
|
|
||||||
|
# canvas.paste: https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.paste
|
||||||
|
# takes mask variable, to identify color system. (not used for pwnagotchi yet)
|
||||||
|
# Pwn should use "1" since its mainly black or white displays.
|
||||||
class Bitmap(Widget):
|
class Bitmap(Widget):
|
||||||
def __init__(self, path, xy, color=0):
|
def __init__(self, path, xy, color=0):
|
||||||
super().__init__(xy, color)
|
super().__init__(xy, color)
|
||||||
self.image = Image.open(path)
|
self.image = Image.open(path)
|
||||||
|
|
||||||
def draw(self, canvas, drawer):
|
def draw(self, canvas, drawer):
|
||||||
|
if self.color == 0xFF:
|
||||||
|
self.image = ImageOps.invert(self.image)
|
||||||
canvas.paste(self.image, self.xy)
|
canvas.paste(self.image, self.xy)
|
||||||
|
|
||||||
|
|
||||||
@ -40,21 +44,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):
|
||||||
|
@ -21,13 +21,47 @@ class Display(View):
|
|||||||
self._canvas_next = None
|
self._canvas_next = None
|
||||||
self._render_thread_instance = threading.Thread(
|
self._render_thread_instance = threading.Thread(
|
||||||
target=self._render_thread,
|
target=self._render_thread,
|
||||||
daemon=True
|
daemon=True,
|
||||||
|
name="Renderer"
|
||||||
)
|
)
|
||||||
self._render_thread_instance.start()
|
self._render_thread_instance.start()
|
||||||
|
|
||||||
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 +152,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 +188,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 +236,12 @@ class Display(View):
|
|||||||
def is_inky(self):
|
def is_inky(self):
|
||||||
return self._implementation.name == 'inky'
|
return self._implementation.name == 'inky'
|
||||||
|
|
||||||
|
def is_inkyv2(self):
|
||||||
|
return self._implementation.name == 'inkyv2'
|
||||||
|
|
||||||
|
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,11 +257,32 @@ 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_waveshareoledlcdvert(self):
|
||||||
|
return self._implementation.name == 'waveshareoledlcdvert'
|
||||||
|
|
||||||
|
def is_i2coled(self):
|
||||||
|
return self._implementation.name == 'i2coled'
|
||||||
|
|
||||||
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() or self.is_waveshare_v4() or self.is_waveshare_v3
|
||||||
|
|
||||||
def init_display(self):
|
def init_display(self):
|
||||||
if self._enabled:
|
if self._enabled:
|
||||||
|
@ -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,345 @@
|
|||||||
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'] == 'inkyv2':
|
||||||
|
from pwnagotchi.ui.hw.inkyv2 import InkyV2
|
||||||
|
return InkyV2(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'] == 'waveshareoledlcdvert':
|
||||||
|
from pwnagotchi.ui.hw.waveshareoledlcdvert import Waveshareoledlcdvert
|
||||||
|
return Waveshareoledlcdvert(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'i2coled':
|
||||||
|
from pwnagotchi.ui.hw.i2coled import I2COled
|
||||||
|
return I2COled(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)
|
@ -3,6 +3,7 @@ import pwnagotchi.ui.fonts as fonts
|
|||||||
|
|
||||||
class DisplayImpl(object):
|
class DisplayImpl(object):
|
||||||
def __init__(self, config, name):
|
def __init__(self, config, name):
|
||||||
|
self._display = None
|
||||||
if fonts.Medium is None:
|
if fonts.Medium is None:
|
||||||
fonts.init(config)
|
fonts.init(config)
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -3,41 +3,41 @@ import logging
|
|||||||
import pwnagotchi.ui.fonts as fonts
|
import pwnagotchi.ui.fonts as fonts
|
||||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
class DFRobotV1(DisplayImpl):
|
class DFRobotV1(DisplayImpl):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super(DFRobotV1, self).__init__(config, 'dfrobot_1')
|
super(DFRobotV1, self).__init__(config, 'dfrobot_1')
|
||||||
self._display = None
|
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
self._layout['width'] = 250
|
self._layout['width'] = 250
|
||||||
self._layout['height'] = 122
|
self._layout['height'] = 122
|
||||||
self._layout['face'] = (0, 40)
|
self._layout['face'] = (0, 40)
|
||||||
self._layout['name'] = (5, 20)
|
self._layout['name'] = (5, 20)
|
||||||
self._layout['channel'] = (0, 0)
|
self._layout['channel'] = (0, 0)
|
||||||
self._layout['aps'] = (28, 0)
|
self._layout['aps'] = (28, 0)
|
||||||
self._layout['uptime'] = (185, 0)
|
self._layout['uptime'] = (185, 0)
|
||||||
self._layout['line1'] = [0, 14, 250, 14]
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
self._layout['line2'] = [0, 108, 250, 108]
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
self._layout['friend_face'] = (0, 92)
|
self._layout['friend_face'] = (0, 92)
|
||||||
self._layout['friend_name'] = (40, 94)
|
self._layout['friend_name'] = (40, 94)
|
||||||
self._layout['shakes'] = (0, 109)
|
self._layout['shakes'] = (0, 109)
|
||||||
self._layout['mode'] = (225, 109)
|
self._layout['mode'] = (225, 109)
|
||||||
self._layout['status'] = {
|
self._layout['status'] = {
|
||||||
'pos': (125, 20),
|
'pos': (125, 20),
|
||||||
'font': fonts.status_font(fonts.Medium),
|
'font': fonts.status_font(fonts.Medium),
|
||||||
'max': 20
|
'max': 20
|
||||||
}
|
}
|
||||||
return self._layout
|
return self._layout
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("initializing dfrobot1 display")
|
logging.info("initializing dfrobot1 display")
|
||||||
from pwnagotchi.ui.hw.libs.dfrobot.v1.dfrobot import DFRobot
|
from pwnagotchi.ui.hw.libs.dfrobot.v1.dfrobot import DFRobot
|
||||||
self._display = DFRobot()
|
self._display = DFRobot()
|
||||||
|
|
||||||
def render(self, canvas):
|
def render(self, canvas):
|
||||||
buf = self._display.getbuffer(canvas)
|
buf = self._display.getbuffer(canvas)
|
||||||
self._display.display(buf)
|
self._display.display(buf)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self._display.Clear(0xFF)
|
self._display.clear(0xFF)
|
||||||
|
@ -3,41 +3,41 @@ import logging
|
|||||||
import pwnagotchi.ui.fonts as fonts
|
import pwnagotchi.ui.fonts as fonts
|
||||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
class DFRobotV1(DisplayImpl):
|
class DFRobotV1(DisplayImpl):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super(DFRobotV1, self).__init__(config, 'dfrobot_1')
|
super(DFRobotV1, self).__init__(config, 'dfrobot_1')
|
||||||
self._display = None
|
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
self._layout['width'] = 250
|
self._layout['width'] = 250
|
||||||
self._layout['height'] = 122
|
self._layout['height'] = 122
|
||||||
self._layout['face'] = (0, 40)
|
self._layout['face'] = (0, 40)
|
||||||
self._layout['name'] = (5, 20)
|
self._layout['name'] = (5, 20)
|
||||||
self._layout['channel'] = (0, 0)
|
self._layout['channel'] = (0, 0)
|
||||||
self._layout['aps'] = (28, 0)
|
self._layout['aps'] = (28, 0)
|
||||||
self._layout['uptime'] = (185, 0)
|
self._layout['uptime'] = (185, 0)
|
||||||
self._layout['line1'] = [0, 14, 250, 14]
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
self._layout['line2'] = [0, 108, 250, 108]
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
self._layout['friend_face'] = (0, 92)
|
self._layout['friend_face'] = (0, 92)
|
||||||
self._layout['friend_name'] = (40, 94)
|
self._layout['friend_name'] = (40, 94)
|
||||||
self._layout['shakes'] = (0, 109)
|
self._layout['shakes'] = (0, 109)
|
||||||
self._layout['mode'] = (225, 109)
|
self._layout['mode'] = (225, 109)
|
||||||
self._layout['status'] = {
|
self._layout['status'] = {
|
||||||
'pos': (125, 20),
|
'pos': (125, 20),
|
||||||
'font': fonts.status_font(fonts.Medium),
|
'font': fonts.status_font(fonts.Medium),
|
||||||
'max': 20
|
'max': 20
|
||||||
}
|
}
|
||||||
return self._layout
|
return self._layout
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("initializing dfrobot1 display")
|
logging.info("initializing dfrobot1 display")
|
||||||
from pwnagotchi.ui.hw.libs.dfrobot.v1.dfrobot import DFRobot
|
from pwnagotchi.ui.hw.libs.dfrobot.v1.dfrobot import DFRobot
|
||||||
self._display = DFRobot()
|
self._display = DFRobot()
|
||||||
|
|
||||||
def render(self, canvas):
|
def render(self, canvas):
|
||||||
buf = self._display.getbuffer(canvas)
|
buf = self._display.getbuffer(canvas)
|
||||||
self._display.display(buf)
|
self._display.display(buf)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self._display.Clear(0xFF)
|
self._display.clear(0xFF)
|
||||||
|
@ -3,41 +3,41 @@ import logging
|
|||||||
import pwnagotchi.ui.fonts as fonts
|
import pwnagotchi.ui.fonts as fonts
|
||||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
class DFRobotV2(DisplayImpl):
|
class DFRobotV2(DisplayImpl):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super(DFRobotV2, self).__init__(config, 'dfrobot_2')
|
super(DFRobotV2, self).__init__(config, 'dfrobot_2')
|
||||||
self._display = None
|
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
self._layout['width'] = 250
|
self._layout['width'] = 250
|
||||||
self._layout['height'] = 122
|
self._layout['height'] = 122
|
||||||
self._layout['face'] = (0, 40)
|
self._layout['face'] = (0, 40)
|
||||||
self._layout['name'] = (5, 20)
|
self._layout['name'] = (5, 20)
|
||||||
self._layout['channel'] = (0, 0)
|
self._layout['channel'] = (0, 0)
|
||||||
self._layout['aps'] = (28, 0)
|
self._layout['aps'] = (28, 0)
|
||||||
self._layout['uptime'] = (185, 0)
|
self._layout['uptime'] = (185, 0)
|
||||||
self._layout['line1'] = [0, 14, 250, 14]
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
self._layout['line2'] = [0, 108, 250, 108]
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
self._layout['friend_face'] = (0, 92)
|
self._layout['friend_face'] = (0, 92)
|
||||||
self._layout['friend_name'] = (40, 94)
|
self._layout['friend_name'] = (40, 94)
|
||||||
self._layout['shakes'] = (0, 109)
|
self._layout['shakes'] = (0, 109)
|
||||||
self._layout['mode'] = (225, 109)
|
self._layout['mode'] = (225, 109)
|
||||||
self._layout['status'] = {
|
self._layout['status'] = {
|
||||||
'pos': (125, 20),
|
'pos': (125, 20),
|
||||||
'font': fonts.status_font(fonts.Medium),
|
'font': fonts.status_font(fonts.Medium),
|
||||||
'max': 20
|
'max': 20
|
||||||
}
|
}
|
||||||
return self._layout
|
return self._layout
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("initializing dfrobot2 display")
|
logging.info("initializing dfrobot2 display")
|
||||||
from pwnagotchi.ui.hw.libs.dfrobot.v2.dfrobot import DFRobot
|
from pwnagotchi.ui.hw.libs.dfrobot.v2.dfrobot import DFRobot
|
||||||
self._display = DFRobot()
|
self._display = DFRobot()
|
||||||
|
|
||||||
def render(self, canvas):
|
def render(self, canvas):
|
||||||
buf = self._display.getbuffer(canvas)
|
buf = self._display.getbuffer(canvas)
|
||||||
self._display.display(buf)
|
self._display.display(buf)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self._display.Clear(0xFF)
|
self._display.clear(0xFF)
|
||||||
|
@ -3,41 +3,41 @@ import logging
|
|||||||
import pwnagotchi.ui.fonts as fonts
|
import pwnagotchi.ui.fonts as fonts
|
||||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
class DFRobotV2(DisplayImpl):
|
class DFRobotV2(DisplayImpl):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super(DFRobotV2, self).__init__(config, 'dfrobot_2')
|
super(DFRobotV2, self).__init__(config, 'dfrobot_2')
|
||||||
self._display = None
|
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
self._layout['width'] = 250
|
self._layout['width'] = 250
|
||||||
self._layout['height'] = 122
|
self._layout['height'] = 122
|
||||||
self._layout['face'] = (0, 40)
|
self._layout['face'] = (0, 40)
|
||||||
self._layout['name'] = (5, 20)
|
self._layout['name'] = (5, 20)
|
||||||
self._layout['channel'] = (0, 0)
|
self._layout['channel'] = (0, 0)
|
||||||
self._layout['aps'] = (28, 0)
|
self._layout['aps'] = (28, 0)
|
||||||
self._layout['uptime'] = (185, 0)
|
self._layout['uptime'] = (185, 0)
|
||||||
self._layout['line1'] = [0, 14, 250, 14]
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
self._layout['line2'] = [0, 108, 250, 108]
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
self._layout['friend_face'] = (0, 92)
|
self._layout['friend_face'] = (0, 92)
|
||||||
self._layout['friend_name'] = (40, 94)
|
self._layout['friend_name'] = (40, 94)
|
||||||
self._layout['shakes'] = (0, 109)
|
self._layout['shakes'] = (0, 109)
|
||||||
self._layout['mode'] = (225, 109)
|
self._layout['mode'] = (225, 109)
|
||||||
self._layout['status'] = {
|
self._layout['status'] = {
|
||||||
'pos': (125, 20),
|
'pos': (125, 20),
|
||||||
'font': fonts.status_font(fonts.Medium),
|
'font': fonts.status_font(fonts.Medium),
|
||||||
'max': 20
|
'max': 20
|
||||||
}
|
}
|
||||||
return self._layout
|
return self._layout
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("initializing dfrobot2 display")
|
logging.info("initializing dfrobot2 display")
|
||||||
from pwnagotchi.ui.hw.libs.dfrobot.v2.dfrobot import DFRobot
|
from pwnagotchi.ui.hw.libs.dfrobot.v2.dfrobot import DFRobot
|
||||||
self._display = DFRobot()
|
self._display = DFRobot()
|
||||||
|
|
||||||
def render(self, canvas):
|
def render(self, canvas):
|
||||||
buf = self._display.getbuffer(canvas)
|
buf = self._display.getbuffer(canvas)
|
||||||
self._display.display(buf)
|
self._display.display(buf)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self._display.Clear(0xFF)
|
self._display.clear(0xFF)
|
||||||
|
@ -7,7 +7,7 @@ from pwnagotchi.ui.hw.base import DisplayImpl
|
|||||||
class DisplayHatMini(DisplayImpl):
|
class DisplayHatMini(DisplayImpl):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super(DisplayHatMini, self).__init__(config, 'displayhatmini')
|
super(DisplayHatMini, self).__init__(config, 'displayhatmini')
|
||||||
self._display = None
|
self.mode = "RGB" # its actually BGR;16 5,6,5 bit, but display lib converts it
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
fonts.setup(12, 10, 12, 70, 25, 9)
|
fonts.setup(12, 10, 12, 70, 25, 9)
|
||||||
@ -35,10 +35,10 @@ 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, width=self._layout['width'], height=self._layout['height'], rotation=0)
|
||||||
|
|
||||||
def render(self, canvas):
|
def render(self, canvas):
|
||||||
self._display.display(canvas)
|
self._display.display(canvas)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self._display.clear()
|
pass
|
||||||
|
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
|
67
pwnagotchi/ui/hw/i2coled.py
Normal file
67
pwnagotchi/ui/hw/i2coled.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Created for the Pwnagotchi project by RasTacsko
|
||||||
|
# HW libraries are based on the adafruit python SSD1306 repo:
|
||||||
|
# https://github.com/adafruit/Adafruit_Python_SSD1306
|
||||||
|
# SMBus parts coming from BLavery's lib_oled96 repo:
|
||||||
|
# https://github.com/BLavery/lib_oled96
|
||||||
|
# I2C address, width and height import from config.toml made by NurseJackass
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
#
|
||||||
|
# Default is 128x64 display on i2c address 0x3C
|
||||||
|
#
|
||||||
|
# Configure i2c address and dimensions in config.toml:
|
||||||
|
#
|
||||||
|
# ui.display.type = "i2coled"
|
||||||
|
# ui.display.i2c_addr = 0x3C
|
||||||
|
# ui.display.width = 128
|
||||||
|
# ui.display.height = 64
|
||||||
|
#
|
||||||
|
|
||||||
|
class I2COled(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
self._config = config['ui']['display']
|
||||||
|
super(I2COled, self).__init__(config, 'i2coled')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(8, 8, 8, 10, 10, 8)
|
||||||
|
self._layout['width'] = self._config['width'] if 'width' in self._config else 128
|
||||||
|
self._layout['height'] = self._config['height'] if 'height' in self._config else 64
|
||||||
|
self._layout['face'] = (0, 30)
|
||||||
|
self._layout['name'] = (0, 10)
|
||||||
|
self._layout['channel'] = (72, 10)
|
||||||
|
self._layout['aps'] = (0, 0)
|
||||||
|
self._layout['uptime'] = (87, 0)
|
||||||
|
self._layout['line1'] = [0, 9, 128, 9]
|
||||||
|
self._layout['line2'] = [0, 54, 128, 54]
|
||||||
|
self._layout['friend_face'] = (0, 41)
|
||||||
|
self._layout['friend_name'] = (40, 43)
|
||||||
|
self._layout['shakes'] = (0, 55)
|
||||||
|
self._layout['mode'] = (107, 10)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (37, 19),
|
||||||
|
'font': fonts.status_font(fonts.Small),
|
||||||
|
'max': 18
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
i2caddr = self._config['i2c_addr'] if 'i2c_addr' in self._config else 0x3C
|
||||||
|
width = self._config['width'] if 'width' in self._config else 128
|
||||||
|
height = self._config['height'] if 'height' in self._config else 64
|
||||||
|
|
||||||
|
logging.info("initializing %dx%d I2C Oled Display on address 0x%X" % (width, height, i2caddr))
|
||||||
|
|
||||||
|
from pwnagotchi.ui.hw.libs.i2coled.epd import EPD
|
||||||
|
self._display = EPD(address=i2caddr, width=width, height=height)
|
||||||
|
self._display.Init()
|
||||||
|
self._display.Clear()
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
self._display.display(canvas)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.Clear()
|
@ -7,7 +7,6 @@ from pwnagotchi.ui.hw.base import DisplayImpl
|
|||||||
class Inky(DisplayImpl):
|
class Inky(DisplayImpl):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super(Inky, self).__init__(config, 'inky')
|
super(Inky, self).__init__(config, 'inky')
|
||||||
self._display = None
|
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
fonts.setup(10, 8, 10, 28, 25, 9)
|
fonts.setup(10, 8, 10, 28, 25, 9)
|
||||||
@ -39,7 +38,7 @@ class Inky(DisplayImpl):
|
|||||||
logging.info("THIS MAY BE POTENTIALLY DANGEROUS. NO WARRANTY IS PROVIDED")
|
logging.info("THIS MAY BE POTENTIALLY DANGEROUS. NO WARRANTY IS PROVIDED")
|
||||||
logging.info("USE THIS DISPLAY IN THIS MODE AT YOUR OWN RISK")
|
logging.info("USE THIS DISPLAY IN THIS MODE AT YOUR OWN RISK")
|
||||||
|
|
||||||
from pwnagotchi.ui.hw.libs.inkyphat.inkyphatfast import InkyPHATFast
|
from pwnagotchi.ui.hw.libs.pimoroni.inkyphat.inkyphatfast import InkyPHATFast
|
||||||
self._display = InkyPHATFast('black')
|
self._display = InkyPHATFast('black')
|
||||||
self._display.set_border(InkyPHATFast.BLACK)
|
self._display.set_border(InkyPHATFast.BLACK)
|
||||||
elif self.config['color'] == 'auto':
|
elif self.config['color'] == 'auto':
|
||||||
@ -85,4 +84,5 @@ class Inky(DisplayImpl):
|
|||||||
logging.exception("error while rendering on inky")
|
logging.exception("error while rendering on inky")
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self._display.Clear()
|
pass
|
||||||
|
# self._display.clear()
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user