Compare commits
354 Commits
Author | SHA1 | Date | |
---|---|---|---|
9fc737a590 | |||
be8809e62b | |||
3155a36fd9 | |||
eb77f29135 | |||
|
c34aed94c0 | ||
eb5e2f77c8 | |||
d2b22b2ff3 | |||
40cdfa3fdd | |||
29aa46a468 | |||
fe0b23625b | |||
e73fe60023 | |||
c35a707201 | |||
f82ac001b0 | |||
1097238319 | |||
ef717734af | |||
1976c8a850 | |||
5147d0f351 | |||
|
a6edbedbc8 | ||
|
4ad4796de7 | ||
|
584d8d30e0 | ||
|
9c6834f216 | ||
|
d5501d89ca | ||
|
47aee29806 | ||
|
9b95af1e52 | ||
|
c26f92d9dc | ||
|
fdef3bae97 | ||
|
bcb01ce046 | ||
|
8c5e84e1c7 | ||
|
7db5c10cd0 | ||
|
f0c2a39e2d | ||
|
6fb42fc8e8 | ||
|
66c4aea7ca | ||
|
ad1cac4755 | ||
|
77362bc530 | ||
|
3e0f3dcb8a | ||
|
71e248a8af | ||
|
366db3e095 | ||
|
001c1942c4 | ||
|
d8a4d930ea | ||
|
81699d3e35 | ||
|
578b15647f | ||
|
4350fa1204 | ||
|
c3093bc9aa | ||
|
3026ec9aa3 | ||
|
188cb6c62b | ||
|
21f4e35f73 | ||
|
7142e2bfd5 | ||
|
38e7eaae73 | ||
|
2ff553cbc6 | ||
|
3cdcd41fa2 | ||
|
88e8efa4df | ||
|
50a88efe15 | ||
|
b09eab24d8 | ||
|
ed6d59ab26 | ||
|
b04fba4bd2 | ||
|
95e96cc42b | ||
|
bb31085a73 | ||
|
be6c55a8cf | ||
|
5c5562f98f | ||
|
1842f88239 | ||
|
ba8f3bc4be | ||
|
7769f249f9 | ||
|
759c5832b4 | ||
|
3cf574bfc7 | ||
|
42e4a776ae | ||
|
bbbe086007 | ||
|
c9fe15005d | ||
|
334f2a4a5c | ||
|
c14019535f | ||
|
cd668f4dd4 | ||
|
cd50cf7418 | ||
|
bddb630b90 | ||
|
e862b24382 | ||
|
4c2d2196c4 | ||
|
b731f5808b | ||
|
4499e419f1 | ||
|
c3b0c9a032 | ||
|
5d93ad18c4 | ||
|
5738a532ec | ||
|
7998a84ea7 | ||
|
cde5396e85 | ||
|
a5d5533acf | ||
|
3c678104ef | ||
|
5ec621c5d7 | ||
|
9ec2646347 | ||
|
713fe6878b | ||
|
20d80bfb35 | ||
|
b2ecebc24a | ||
|
298ed24008 | ||
|
a9f07e9f8d | ||
|
ff91f9b2f8 | ||
|
07b70dd0ee | ||
|
c12b319287 | ||
|
712d0142a1 | ||
|
9ba55b7f77 | ||
|
abc16ce973 | ||
|
1318275b36 | ||
|
297d9cd3b9 | ||
|
f164b8bb26 | ||
|
5c3b21f537 | ||
|
6325428218 | ||
|
7d35f5cdc0 | ||
|
3fd3ac3c01 | ||
|
c1d3528ff7 | ||
|
37fc65f834 | ||
|
80095edf4b | ||
|
7e3e74635a | ||
|
6b3d9042fd | ||
|
4441ae852c | ||
|
edc0551e0a | ||
|
b6cf510f8e | ||
|
7fb6be861a | ||
|
f795598950 | ||
|
b0efd52961 | ||
|
38dfccb7c2 | ||
|
f912883f6e | ||
|
661f26dedf | ||
|
d6c7a73f39 | ||
|
10f274dab7 | ||
|
decbeaccb1 | ||
|
34c2c8a06e | ||
|
88a15528d6 | ||
|
0fd09878db | ||
|
c472e60615 | ||
|
0704541dd1 | ||
|
ea061d473b | ||
|
6430a40847 | ||
|
ba13b12593 | ||
|
819be761ed | ||
|
f701390d5a | ||
|
2ddf040fac | ||
|
3bd9cd4f18 | ||
|
5c6d8dc807 | ||
|
c535ffd40e | ||
|
fb7217b0fa | ||
|
7da3cc5565 | ||
|
e72e2292a8 | ||
|
0f8643258e | ||
|
1a0083eb38 | ||
|
72878454f9 | ||
|
1c4df7a1c4 | ||
|
c124a97514 | ||
|
b886b4e673 | ||
|
6d0e295276 | ||
|
1aea0b95b1 | ||
|
fddee8708e | ||
|
ef4fbd96cc | ||
|
552df65422 | ||
|
2db8f143eb | ||
|
6111ee9d9d | ||
|
713a14c504 | ||
|
2708fd032c | ||
|
f7cf4b3947 | ||
|
1d312a727b | ||
|
dd3dbbe400 | ||
|
cfa034b555 | ||
|
a0cd0d4936 | ||
|
3e3fff298c | ||
|
0b1c51dcc7 | ||
|
8dd9a85615 | ||
|
929eac7bba | ||
|
0f7870f770 | ||
|
37342c0685 | ||
|
71514a97fa | ||
|
03488819af | ||
|
633b726bcd | ||
|
05b235c38b | ||
|
840054f549 | ||
|
a3cf49244e | ||
|
e92751164f | ||
|
c2f9860bc5 | ||
|
f616871068 | ||
|
c726779f6e | ||
|
a2e29d64d2 | ||
|
bc84f22f7a | ||
|
40d8d994d1 | ||
|
7ca5eee247 | ||
|
56c291d302 | ||
|
1013e7dc19 | ||
|
ff4f5c672a | ||
|
d9d268ea81 | ||
|
de62214970 | ||
|
52cc413152 | ||
|
716d5cd312 | ||
|
e436dc8b8e | ||
|
81db495f33 | ||
|
7f8380c38a | ||
|
8c2b4e2075 | ||
|
2b17e5322b | ||
|
1be17b1f99 | ||
|
2dee3987d4 | ||
|
eb76cc7023 | ||
|
35ea36ef33 | ||
|
44e1e79245 | ||
|
6038f555fa | ||
|
0b5a63a3d8 | ||
|
430172e3dd | ||
|
fa87e03222 | ||
|
d1411ffa96 | ||
|
67b4747afa | ||
|
311931c81d | ||
|
1f7bc60de6 | ||
|
57034d9fc6 | ||
|
74fbf4da32 | ||
|
7ec20caf23 | ||
|
568c5b020d | ||
|
3965bdb554 | ||
|
585b208e9e | ||
|
e53bdc46a4 | ||
|
6805df858e | ||
|
8a07e822e6 | ||
|
5f7dd56ead | ||
|
a808fd33c7 | ||
|
cedcc17391 | ||
|
6478b3827d | ||
|
68065d548a | ||
|
463f4b50e4 | ||
|
793cde7147 | ||
|
518b9e665d | ||
|
91ea7bdb9b | ||
|
3ce88f1d80 | ||
|
6d45d01baf | ||
|
1f2dd73976 | ||
|
5d8d86204a | ||
|
7017e39c6d | ||
|
1360c734ff | ||
|
7c90050b17 | ||
|
9ca8aacdf6 | ||
|
d39c849daf | ||
|
58bbae89c2 | ||
|
0dedd0974f | ||
|
5bac678771 | ||
|
3b9aacdd16 | ||
|
305f837486 | ||
|
54ffbbcb0b | ||
|
60167fb8fe | ||
|
9a1565813c | ||
|
03c014f414 | ||
|
d10bf6bf1d | ||
|
9a22321799 | ||
|
76b71f5c3f | ||
|
4aa05bb834 | ||
|
71c4458858 | ||
|
8260b41bab | ||
|
86530d4b97 | ||
|
c7d9a757f6 | ||
|
b6a0ae9d3a | ||
|
34f52b0d3a | ||
|
c68cefe80d | ||
|
052c99b858 | ||
|
ccea7cbeef | ||
|
a16a5f7bcb | ||
|
a5df77d737 | ||
|
489bce0c01 | ||
|
da4319f81b | ||
|
0e1a1f4c79 | ||
|
b3bdb34e3f | ||
|
c791c86bee | ||
|
61e5872229 | ||
|
23616095ba | ||
|
3c8e7fbea4 | ||
|
4a5d2d36cc | ||
|
52d432e5b6 | ||
|
93bdf2e3a1 | ||
|
fe97315b0f | ||
|
7e80a7b9ca | ||
|
64385f43ed | ||
|
6a4d7a895e | ||
|
5606ad7281 | ||
|
23f09bc4b6 | ||
|
238b90d988 | ||
|
138316d55a | ||
|
67bbcfef9b | ||
|
1a0e0c46d2 | ||
|
4cd0f46ad7 | ||
|
155ea54d08 | ||
|
461e53ed79 | ||
|
6d40388002 | ||
|
37b25a142f | ||
|
665ad938b4 | ||
|
301a3d99cf | ||
|
c4e0acad17 | ||
|
9339ecb2fd | ||
|
a28c9a1176 | ||
|
c5d6f6d362 | ||
|
ff843f0367 | ||
|
717cb02743 | ||
|
8be643b2e0 | ||
|
814392daa5 | ||
|
4cc1c2ac1f | ||
|
fae6a0942b | ||
|
53ab63cf8a | ||
|
0764304be9 | ||
|
cdc0e0fa3e | ||
|
5ccd65e46e | ||
|
afc3636939 | ||
|
f154b97ab9 | ||
|
66acecb387 | ||
|
a31d0a5e19 | ||
|
026b9fc513 | ||
|
1cdc1641fc | ||
|
71de5925ee | ||
|
4733e90e77 | ||
|
7cf0a2ef4b | ||
|
97e03843bd | ||
|
8b078383c2 | ||
|
3e5bece3cf | ||
|
7645be6f3e | ||
|
779da95f78 | ||
|
51e13aa1ad | ||
|
e489678cf5 | ||
|
52015014b4 | ||
|
f691f737ab | ||
|
2617a6edea | ||
|
6001b7630b | ||
|
78fba1f74b | ||
|
6075296884 | ||
|
9457622713 | ||
|
7ef1c1f2f0 | ||
|
8e0488e16f | ||
|
5934ac4a55 | ||
|
15bae093fb | ||
|
0c76cd7ea7 | ||
|
5834b27ed8 | ||
|
a8ed9bcc1c | ||
|
0e88c3aa6a | ||
|
b1d61d95e6 | ||
|
215af0fc88 | ||
|
c09b72ff7e | ||
|
2f1b35b3fa | ||
|
d435ef2ba9 | ||
|
4164e7c067 | ||
|
bb7737762c | ||
|
c300e73726 | ||
|
0587c4b09a | ||
|
63dc672b11 | ||
|
0dac137df0 | ||
|
3db9ccb47e | ||
|
f375e4905f | ||
|
8d17cf0bd2 | ||
|
d981b26842 | ||
|
a0bc911c0e | ||
|
6d71bcd965 | ||
|
91447a2a31 | ||
|
e06480e474 | ||
|
819146f83a | ||
|
cdd4c13336 | ||
|
704d7ceaa1 | ||
|
a4daf4af61 | ||
|
eddcf32b62 | ||
|
6117235c52 | ||
|
10f7161240 | ||
|
9b02548176 | ||
|
f5f47c4f88 |
@ -1,7 +1,6 @@
|
||||
maintainers:
|
||||
- evilsocket
|
||||
- caquino
|
||||
- dadav
|
||||
- justin-p
|
||||
|
||||
features:
|
||||
|
@ -1,7 +1,31 @@
|
||||
# top-most EditorConfig file
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
# Matches the exact files either package.json or .travis.yml
|
||||
[{*.yml,*.yaml,config.yml,defaults.yml}]
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.json]
|
||||
insert_final_newline = ignore
|
||||
|
||||
[*.js]
|
||||
indent_style = ignore
|
||||
insert_final_newline = ignore
|
||||
|
||||
[*.{md,txt}]
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = false
|
||||
|
96
.github/workflows/CreateRelease.yml
vendored
Normal file
96
.github/workflows/CreateRelease.yml
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_version:
|
||||
description: 'Release version'
|
||||
required: true
|
||||
type: string
|
||||
pishrink:
|
||||
description: 'pishrink Script'
|
||||
default: 'https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh'
|
||||
type: string
|
||||
z_compress_args:
|
||||
description: '7z compress args'
|
||||
default: '7z a -t7z -mx=9'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# - name: Set VERSION variable
|
||||
# run: |
|
||||
# VERSION=$(awk '/__version__ /{print $NF}' ./pwnagotchi/_version.py | tr -d "'")
|
||||
# env:
|
||||
# VERSION: ${{ steps.set-version.outputs.version }} # Use the extracted version
|
||||
|
||||
- name: Show Version
|
||||
run: |
|
||||
# Use the $VERSION variable in your build or deployment steps
|
||||
echo "Using VERSION: ${{ inputs.release_version }}"
|
||||
|
||||
- name: Set _version.py correctly using the env variable
|
||||
run: |
|
||||
sed -i "s#.*__version__.*#__version__='$PWN_VERSION'#" pwnagotchi/_version.py
|
||||
env:
|
||||
PWN_VERSION: ${{ inputs.release_version }}
|
||||
|
||||
- name: Install language dependencies
|
||||
run: sudo apt-get install -y gettext
|
||||
|
||||
- name: Languages
|
||||
run: make langs
|
||||
|
||||
- name: Image
|
||||
run: make image
|
||||
env:
|
||||
PWN_VERSION: ${{ inputs.release_version }}
|
||||
|
||||
- name: Shrink Image
|
||||
run: |
|
||||
ls -a -s -h
|
||||
wget ${{ inputs.pishrink }}
|
||||
chmod +x pishrink.sh
|
||||
sudo mv pishrink.sh /usr/local/bin
|
||||
sudo pishrink.sh ./pwnagotchi-${{ inputs.release_version }}.img
|
||||
|
||||
- uses: edgarrc/action-7z@v1
|
||||
with:
|
||||
args: ${{ inputs.z_compress_args }} pwnagotchi-${{ inputs.release_version }}.7z ./pwnagotchi-${{ inputs.release_version }}.img
|
||||
|
||||
- name: sha256sum 7z
|
||||
run: |
|
||||
sudo sha256sum ./pwnagotchi-${{ inputs.release_version }}.7z > ./pwnagotchi-${{ inputs.release_version }}.sha256
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: create_new_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ inputs.release_version }}
|
||||
release_name: Release ${{ inputs.release_version }}
|
||||
- name: Upload GitHub Release sha
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_new_release.outputs.upload_url }}
|
||||
asset_path: ./pwnagotchi-${{ inputs.release_version }}.sha256
|
||||
asset_name: pwnagotchi-v${{ inputs.release_version }}.sha256
|
||||
asset_content_type: appliction/text
|
||||
- name: Upload GitHub Release Zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_new_release.outputs.upload_url }}
|
||||
asset_path: ./pwnagotchi-${{ inputs.release_version }}.7z
|
||||
asset_name: pwnagotchi-v${{ inputs.release_version }}.7z
|
||||
asset_content_type: appliction/zip
|
11
.gitignore
vendored
11
.gitignore
vendored
@ -2,6 +2,7 @@
|
||||
*.img.bmap
|
||||
*.pcap
|
||||
*.po~
|
||||
*.sha256
|
||||
preview.png
|
||||
__pycache__
|
||||
_backups
|
||||
@ -17,3 +18,13 @@ dist
|
||||
pwnagotchi.egg-info
|
||||
*backup*.tgz
|
||||
*backup*.gz
|
||||
.vscode
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
@ -9,7 +9,7 @@ env:
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: vBUokTv94n8s65STUgTiD6I0Iy8KXbBRvQUrAof8XG+U4ZMsH5PmDTpS+wz+SaxI6o0PRkfyOiPVdARhiKAFnfatG3q9EHllMQwqRR2YIju51A3aCxgEJ5uWDoybwQdipERUMMYwUO/8XZaRRpwFD2bdQBFWkBtQyMcAkrEL8BXckwQQ531oDN2hK5gAiTllqsOswV2idwUlBRU9jOtStzff+UgUYsp/ZebsRodyOYkEB2Ev15yARo2HTXbyZ2icwHPtMbx5zmNUSRtxs9a4hfzaK3m6ctK8qLYYUdQvXub/ruuACapdw4Ez88LY1agTecbZhFYmJzv8oANH1e4VUI4owuHnZCpU6LRutS4wOhglrkOrGo6lSUlJeA+RtQjyjBugjej9DDtDyyIlRU1ZaBF3qWR9N5EXKuquf0olOfmUR67ap1NykE9VUpzkYjkoVRTiPs/e2onM/nRNOvAQcIt75FD13u+Y/DcYQ8r7KpMIu1HNdtbVx8gMeq76bRhP1YdDg2jm+DdJ21KWjf5QHsbyoXDfJzdKlCloLIlAU3EPJhMoXsnNzre0/FXeUl6dfteR1axNS6U7e/vKsQ9rlUFZWIQaeVPjfXmFKblNNVQ5uFrrsB/EGHcJl7IUx5fvcRT5hMMNwC660YxVkBXDbRb5fxMW5/+K0BOi9cP6en8=
|
||||
secure: "Rj0QnEDv02UzjKaxHHxJ/Sdj50EOFIrsKShr27GtVNSwHmNKxQuwlh31T0DQdif4wzcDtUZ5XWxr85vLkKJt1L8anb7Tb63qlu7Da69C+upSUow1uJkjsiScbMPqwHpwDIkkcUIbPsdbowI7qiNhRBD7nF8yyLPC5YLiU0cXKuGh4+Q5xdIlL3P7p8jm0919Y+olglzAZj0iNR/QxGOb+laNH8xi0oUsIPi5V0ZFfO/W/sm+nks9ki5nolfd1ML1gcbOD7uKuxIMTUrpDLl4p2Jx9IVQW+G2/tkmNLbP5Ga65NxQcfABQDYY3tCD8PsmFK9PEwa4cMbGJjqlo3yR7P21J5Aj3rK+L4KDntOvwem3Z3Y/v2JlQZn+gelhNFCxuPBi3ZihSf7POMHtpAYmi13N2ruzOg1ayjeYph0iN3vXIPs67DpAPaxK+8L2yoo6Nr/Cago9pGTkZoqS+J0fnWT31NXoYREPgg//L2+m42twQirFXttbhlGTBgNMLXpwcm8bZ2DW3pu3AEgVUxSoNAOjudoeyC0VzA6nUqe6STmfk06OYqcwM8q8NEyD62iAvUYU3Q7FnauZqcBqcP+ZYx82NPZybrQRX6YlJck5UomHbbEfjgpDFT+WvjrrfICmXH29YBOL1LWR4cKMT6RY58Cv8hT2PYxomB2I+DRrbqU="
|
||||
skip_cleanup: true
|
||||
file_glob: true
|
||||
file:
|
||||
|
@ -1,9 +1,10 @@
|
||||
exclude *.pyc .DS_Store .gitignore MANIFEST.in
|
||||
include requirements.txt
|
||||
include setup.py
|
||||
include distribute_setup.py
|
||||
include README.md
|
||||
include LICENSE
|
||||
recursive-include bin *
|
||||
recursive-include builder/data *
|
||||
recursive-include pwnagotchi *.py
|
||||
recursive-include pwnagotchi *.yml
|
||||
recursive-include pwnagotchi *.*
|
||||
|
90
Makefile
90
Makefile
@ -1,23 +1,79 @@
|
||||
PWN_HOSTNAME=pwnagotchi
|
||||
PWN_VERSION=master
|
||||
PACKER_VERSION := 1.8.5
|
||||
PWN_HOSTNAME := pwnagotchi
|
||||
# PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py)
|
||||
PWN_VERSION := $(or ${PWN_VERSION},$(shell cut -d"'" -f2 < pwnagotchi/_version.py))
|
||||
PWN_RELEASE := pwnagotchi-$(PWN_VERSION)
|
||||
|
||||
MACHINE_TYPE := $(shell uname -m)
|
||||
ifneq (,$(filter x86_64,$(MACHINE_TYPE)))
|
||||
GOARCH := amd64
|
||||
else ifneq (,$(filter i686,$(MACHINE_TYPE)))
|
||||
GOARCH := 386
|
||||
else ifneq (,$(filter arm64% aarch64%,$(MACHINE_TYPE)))
|
||||
GOARCH := arm64
|
||||
else ifneq (,$(filter arm%,$(MACHINE_TYPE)))
|
||||
GOARCH := arm
|
||||
else
|
||||
GOARCH := amd64
|
||||
$(warning Unable to detect CPU arch from machine type $(MACHINE_TYPE), assuming $(GOARCH))
|
||||
endif
|
||||
|
||||
# The Ansible part of the build can inadvertently change the active hostname of
|
||||
# the build machine while updating the permanent hostname of the build image.
|
||||
# If the unshare command is available, use it to create a separate namespace
|
||||
# so hostname changes won't affect the build machine.
|
||||
UNSHARE := $(shell command -v unshare)
|
||||
ifneq (,$(UNSHARE))
|
||||
UNSHARE := $(UNSHARE) --uts
|
||||
endif
|
||||
|
||||
all: clean install image
|
||||
|
||||
install:
|
||||
curl https://releases.hashicorp.com/packer/1.3.5/packer_1.3.5_linux_amd64.zip -o /tmp/packer.zip
|
||||
unzip /tmp/packer.zip -d /tmp
|
||||
sudo mv /tmp/packer /usr/bin/packer
|
||||
git clone https://github.com/solo-io/packer-builder-arm-image /tmp/packer-builder-arm-image
|
||||
cd /tmp/packer-builder-arm-image && go get -d ./... && go build
|
||||
sudo cp /tmp/packer-builder-arm-image/packer-builder-arm-image /usr/bin
|
||||
langs:
|
||||
@for lang in pwnagotchi/locale/*/; do\
|
||||
echo "compiling language: $$lang ..."; \
|
||||
./scripts/language.sh compile $$(basename $$lang); \
|
||||
done
|
||||
|
||||
image:
|
||||
cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json
|
||||
sudo mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img
|
||||
sudo sha256sum pwnagotchi-raspbian-lite-$(PWN_VERSION).img > pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256
|
||||
sudo zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 pwnagotchi-raspbian-lite-$(PWN_VERSION).img
|
||||
install:
|
||||
PACKER := /tmp/pwnagotchi/packer
|
||||
PACKER_URL := https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_$(GOARCH).zip
|
||||
$(PACKER):
|
||||
mkdir -p $(@D)
|
||||
curl -L "$(PACKER_URL)" -o $(PACKER).zip
|
||||
unzip $(PACKER).zip -d $(@D)
|
||||
rm $(PACKER).zip
|
||||
chmod +x $@
|
||||
|
||||
SDIST := dist/pwnagotchi-$(PWN_VERSION).tar.gz
|
||||
$(SDIST): setup.py pwnagotchi
|
||||
python3 setup.py sdist
|
||||
|
||||
# Building the image requires packer, but don't rebuild the image just because packer updated.
|
||||
$(PWN_RELEASE).img: | $(PACKER)
|
||||
|
||||
# If the packer or ansible files are updated, rebuild the image.
|
||||
$(PWN_RELEASE).img: $(SDIST) builder/pwnagotchi.json builder/pwnagotchi.yml $(shell find builder/data -type f)
|
||||
sudo $(PACKER) plugins install github.com/solo-io/arm-image
|
||||
cd builder && sudo $(UNSHARE) $(PACKER) build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json
|
||||
sudo chown -R $$USER:$$USER builder/output-pwnagotchi
|
||||
mv builder/output-pwnagotchi/image $@
|
||||
|
||||
# If any of these files are updated, rebuild the checksums.
|
||||
$(PWN_RELEASE).sha256: $(PWN_RELEASE).img
|
||||
sha256sum $^ > $@
|
||||
|
||||
# If any of the input files are updated, rebuild the archive.
|
||||
$(PWN_RELEASE).zip: $(PWN_RELEASE).img $(PWN_RELEASE).sha256
|
||||
zip $(PWN_RELEASE).zip $^
|
||||
|
||||
.PHONY: image
|
||||
image: $(PWN_RELEASE).zip
|
||||
|
||||
clean:
|
||||
rm -rf /tmp/packer-builder-arm-image
|
||||
rm -f pwnagotchi-raspbian-lite-*.zip pwnagotchi-raspbian-lite-*.img pwnagotchi-raspbian-lite-*.sha256
|
||||
rm -rf builder/output-pwnagotchi builder/packer_cache
|
||||
- python3 setup.py clean --all
|
||||
- rm -rf dist pwnagotchi.egg-info
|
||||
- rm -f $(PACKER)
|
||||
- rm -f $(PWN_RELEASE).*
|
||||
- sudo rm -rf builder/output-pwnagotchi builder/packer_cache
|
||||
|
||||
|
@ -15,11 +15,11 @@ full and half WPA handshakes.
|
||||
|
||||

|
||||
|
||||
Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to.
|
||||
Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.toml) over time to **get better at pwning WiFi things to** in the environments you expose it to.
|
||||
|
||||
More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://www.pwnagotchi.ai/usage/#training-the-ai) doc.)
|
||||
|
||||
**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but ** listen to your Pwnagotchi when it tells you it's boring!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :)
|
||||
**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but ** listen to your Pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :)
|
||||
|
||||
Multiple units within close physical proximity can "talk" to each other, advertising their presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage.
|
||||
|
||||
|
@ -2,24 +2,23 @@
|
||||
import logging
|
||||
import argparse
|
||||
import time
|
||||
import yaml
|
||||
import signal
|
||||
import sys
|
||||
import toml
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.grid as grid
|
||||
import pwnagotchi.utils as utils
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
from pwnagotchi.identity import KeyPair
|
||||
from pwnagotchi.agent import Agent
|
||||
from pwnagotchi.ui.display import Display
|
||||
from pwnagotchi import utils
|
||||
from pwnagotchi.plugins import cmd as plugins_cmd
|
||||
from pwnagotchi import log
|
||||
from pwnagotchi import restart
|
||||
from pwnagotchi import fs
|
||||
from pwnagotchi.utils import DottedTomlEncoder
|
||||
|
||||
|
||||
def do_clear(display):
|
||||
logging.info("clearing the display ...")
|
||||
display.clear()
|
||||
exit(0)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def do_manual_mode(agent):
|
||||
@ -84,15 +83,21 @@ def do_auto_mode(agent):
|
||||
plugins.on('internet_available', agent)
|
||||
|
||||
except Exception as e:
|
||||
logging.exception("main loop exception")
|
||||
|
||||
if str(e).find("wifi.interface not set") > 0:
|
||||
logging.exception("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")
|
||||
time.sleep(60)
|
||||
agent.next_epoch()
|
||||
else:
|
||||
logging.exception("main loop exception (%s)", e)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser = plugins_cmd.add_parsers(parser)
|
||||
|
||||
parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.yml',
|
||||
parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml',
|
||||
help='Main configuration file.')
|
||||
parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.yml',
|
||||
parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml',
|
||||
help='If this file exists, configuration will be merged and this will override default values.')
|
||||
|
||||
parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
|
||||
@ -113,16 +118,34 @@ if __name__ == '__main__':
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
if plugins_cmd.used_plugin_cmd(args):
|
||||
config = utils.load_config(args)
|
||||
log.setup_logging(args, config)
|
||||
rc = plugins_cmd.handle_cmd(args, config)
|
||||
sys.exit(rc)
|
||||
|
||||
if args.version:
|
||||
print(pwnagotchi.version)
|
||||
exit(0)
|
||||
print(pwnagotchi.__version__)
|
||||
sys.exit(0)
|
||||
|
||||
config = utils.load_config(args)
|
||||
if args.print_config:
|
||||
print(yaml.dump(config, default_flow_style=False))
|
||||
exit(0)
|
||||
|
||||
utils.setup_logging(args, config)
|
||||
if args.print_config:
|
||||
print(toml.dumps(config, encoder=DottedTomlEncoder()))
|
||||
sys.exit(0)
|
||||
|
||||
from pwnagotchi.identity import KeyPair
|
||||
from pwnagotchi.agent import Agent
|
||||
from pwnagotchi.ui import fonts
|
||||
from pwnagotchi.ui.display import Display
|
||||
from pwnagotchi import grid
|
||||
from pwnagotchi import plugins
|
||||
|
||||
pwnagotchi.config = config
|
||||
fs.setup_mounts(config)
|
||||
log.setup_logging(args, config)
|
||||
fonts.init(config)
|
||||
|
||||
pwnagotchi.set_name(config['main']['name'])
|
||||
|
||||
@ -132,7 +155,7 @@ if __name__ == '__main__':
|
||||
|
||||
if args.do_clear:
|
||||
do_clear(display)
|
||||
exit(0)
|
||||
sys.exit(0)
|
||||
|
||||
agent = Agent(view=display, config=config, keypair=KeyPair(view=display))
|
||||
|
||||
|
29
builder/data/etc/bash_completion.d/pwnagotchi_completion.sh
Normal file
29
builder/data/etc/bash_completion.d/pwnagotchi_completion.sh
Normal file
@ -0,0 +1,29 @@
|
||||
_show_complete()
|
||||
{
|
||||
local cur opts node_names all_options opt_line
|
||||
all_options="
|
||||
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config {plugins}
|
||||
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
|
||||
pwnagotchi plugins list -i --installed -h --help
|
||||
pwnagotchi plugins install -h --help
|
||||
pwnagotchi plugins uninstall -h --help
|
||||
pwnagotchi plugins enable -h --help
|
||||
pwnagotchi plugins disable -h --help
|
||||
pwnagotchi plugins update -h --help
|
||||
pwnagotchi plugins upgrade -h --help
|
||||
"
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}"
|
||||
opt_line="$(grep -m1 "$cmd" <<<$all_options)"
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
opts="$(echo $opt_line | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')"
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')"
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
}
|
||||
|
||||
complete -F _show_complete pwnagotchi
|
@ -4,4 +4,5 @@ iface usb0 inet static
|
||||
netmask 255.255.255.0
|
||||
network 10.0.0.0
|
||||
broadcast 10.0.0.255
|
||||
gateway 10.0.0.1
|
||||
gateway 10.0.0.1
|
||||
metric 20
|
||||
|
@ -1,2 +1,4 @@
|
||||
allow-hotplug wlan0
|
||||
iface wlan0 inet static
|
||||
iface wlan0 inet manual
|
||||
pre-up ifconfig $IFACE up
|
||||
post-down ifconfig $IFACE down
|
||||
|
@ -6,12 +6,15 @@ After=pwngrid-peer.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/tmp
|
||||
PermissionsStartOnly=true
|
||||
ExecStart=/usr/bin/pwnagotchi-launcher
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
TasksMax=infinity
|
||||
LimitNPROC=infinity
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
@ -1,8 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
|
||||
# we need to decrypt something
|
||||
if is_crypted_mode; then
|
||||
while ! is_decrypted; do
|
||||
echo "Waiting for decryption..."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
# check if wifi driver is bugged
|
||||
if ! check_brcm; then
|
||||
if ! reload_brcm; then
|
||||
echo "Could not reload wifi driver. Reboot"
|
||||
reboot
|
||||
fi
|
||||
sleep 10
|
||||
fi
|
||||
|
||||
# start mon0
|
||||
start_monitor_interface
|
||||
if ! is_interface_up 'mon0'; then
|
||||
start_monitor_interface
|
||||
else
|
||||
stop_monitor_interface
|
||||
start_monitor_interface
|
||||
fi
|
||||
|
||||
if is_auto_mode_no_delete; then
|
||||
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0
|
||||
|
148
builder/data/usr/bin/decryption-webserver
Executable file
148
builder/data/usr/bin/decryption-webserver
Executable file
@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
|
||||
_HTML_FORM_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Decryption</title>
|
||||
<style>
|
||||
body {{ text-align: center; padding: 150px; }}
|
||||
h1 {{ font-size: 50px; }}
|
||||
body {{ font: 20px Helvetica, sans-serif; color: #333; }}
|
||||
article {{ display: block; text-align: center; width: 650px; margin: 0 auto;}}
|
||||
input {{
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
}}
|
||||
input[type=password] {{
|
||||
width: 75%;
|
||||
font-size: 24px;
|
||||
}}
|
||||
input[type=submit] {{
|
||||
cursor: pointer;
|
||||
width: 75%;
|
||||
}}
|
||||
input[type=submit]:hover {{
|
||||
background-color: #d9d9d9;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<h1>Decryption</h1>
|
||||
<p>Some of your files are encrypted.</p>
|
||||
<p>Please provide the decryption password.</p>
|
||||
<div>
|
||||
<form action="/set-password" method="POST">
|
||||
{password_fields}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
POST_RESPONSE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
/* Center the loader */
|
||||
#loader {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 1;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: -75px 0 0 -75px;
|
||||
border: 16px solid #f3f3f3;
|
||||
border-radius: 50%;
|
||||
border-top: 16px solid #3498db;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
#myDiv {
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
function checkPwnagotchi() {
|
||||
var target = 'http://' + document.location.hostname + ':8080/';
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', target);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200 || xhr.status == 401) {
|
||||
window.location.replace(target);
|
||||
}else{
|
||||
setTimeout(checkPwnagotchi, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
setTimeout(checkPwnagotchi, 1000);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body style="margin:0;">
|
||||
|
||||
<div id="loader"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
HTML_FORM = None
|
||||
|
||||
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML_FORM.encode())
|
||||
|
||||
def do_POST(self):
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
body = self.rfile.read(content_length)
|
||||
for mapping, password in parse_qsl(body.decode('UTF-8')):
|
||||
with open('/tmp/.pwnagotchi-secret-{}'.format(mapping), 'wt') as pwfile:
|
||||
pwfile.write(password)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(POST_RESPONSE.encode())
|
||||
|
||||
|
||||
with open('/root/.pwnagotchi-crypted') as crypted_file:
|
||||
mappings = [line.split()[0] for line in crypted_file.readlines()]
|
||||
fields = ''.join(['<label for="{m}">Passphrase for {m}:</label>\n<input type="password" id="{m}" name="{m}" value=""><br>'.format(m=m)
|
||||
for m in mappings])
|
||||
HTML_FORM = _HTML_FORM_TEMPLATE.format(password_fields=fields)
|
||||
|
||||
httpd = HTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
|
||||
httpd.serve_forever()
|
@ -1,6 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
|
||||
# we need to decrypt something
|
||||
if is_crypted_mode; then
|
||||
while ! is_decrypted; do
|
||||
echo "Waiting for decryption..."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
# blink 10 times to signal ready state
|
||||
blink_led 10 &
|
||||
|
||||
@ -8,4 +16,4 @@ if is_auto_mode; then
|
||||
/usr/local/bin/pwnagotchi
|
||||
else
|
||||
/usr/local/bin/pwnagotchi --manual
|
||||
fi
|
||||
fi
|
||||
|
@ -3,31 +3,79 @@
|
||||
# well ... it blinks the led
|
||||
blink_led() {
|
||||
for i in $(seq 1 "$1"); do
|
||||
echo 0 >/sys/class/leds/led0/brightness
|
||||
if [ -d /sys/class/leds/led0 ]
|
||||
then
|
||||
echo 0 | tee /sys/class/leds/led0/brightness
|
||||
else
|
||||
echo 0 | tee /sys/class/leds/ACT/brightness
|
||||
fi
|
||||
sleep 0.3
|
||||
echo 1 >/sys/class/leds/led0/brightness
|
||||
|
||||
if [ -d /sys/class/leds/led0 ]
|
||||
then
|
||||
echo 1 | tee /sys/class/leds/led0/brightness
|
||||
else
|
||||
echo 1 | tee /sys/class/leds/ACT/brightness
|
||||
fi
|
||||
sleep 0.3
|
||||
|
||||
done
|
||||
echo 0 >/sys/class/leds/led0/brightness
|
||||
|
||||
if [ -d /sys/class/leds/led0 ]
|
||||
then
|
||||
echo 0 | tee /sys/class/leds/led0/brightness
|
||||
else
|
||||
echo 0 | tee /sys/class/leds/ACT/brightness
|
||||
fi
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
# check if brcm is stuck
|
||||
check_brcm() {
|
||||
if [[ "$(journalctl -n10 -k --since -5m | grep -c 'brcmf_cfg80211_nexmon_set_channel.*Set Channel failed')" -ge 5 ]]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# reload mod
|
||||
reload_brcm() {
|
||||
if ! modprobe -r brcmfmac; then
|
||||
return 1
|
||||
fi
|
||||
if ! modprobe brcmfmac; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# starts mon0
|
||||
start_monitor_interface() {
|
||||
iw phy phy0 interface add mon0 type monitor && ifconfig mon0 up
|
||||
rfkill unblock all
|
||||
iw dev wlan0 set power_save off
|
||||
|
||||
ifconfig wlan0 up
|
||||
|
||||
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add mon0 type monitor && ifconfig mon0 up
|
||||
|
||||
# If wlan0 is NOT taken down after bringing up mon0, then when switching to AUTO you will get:
|
||||
# error 400: error while initializing mon0 to channel 1: iw: out=command failed: Device or resource busy (-16) err=exit status 240
|
||||
ifconfig wlan0 down
|
||||
}
|
||||
|
||||
# stops mon0
|
||||
stop_monitor_interface() {
|
||||
ifconfig mon0 down && iw dev mon0 del
|
||||
ifconfig wlan0 up
|
||||
}
|
||||
|
||||
# returns 0 if the specificed network interface is up
|
||||
is_interface_up() {
|
||||
if grep -qi 'up' /sys/class/net/$1/operstate; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# returns 0 if conditions for AUTO mode are met
|
||||
@ -84,4 +132,82 @@ is_auto_mode_no_delete() {
|
||||
|
||||
# no override, but none of the interfaces is up -> AUTO
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
# check if we need to decrypt something
|
||||
is_crypted_mode() {
|
||||
if [ -f /root/.pwnagotchi-crypted ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# decryption loop
|
||||
is_decrypted() {
|
||||
while read -r mapping container mount; do
|
||||
# mapping = name the device or file will be mapped to
|
||||
# container = the luks encrypted device or file
|
||||
# mount = the mountpoint
|
||||
|
||||
# fail if not mounted
|
||||
if ! mountpoint -q "$mount" >/dev/null 2>&1; then
|
||||
if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then
|
||||
</tmp/.pwnagotchi-secret-"$mapping" read -r SECRET
|
||||
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
|
||||
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/dev/null 2>&1; then
|
||||
echo "Container decrypted!"
|
||||
fi
|
||||
fi
|
||||
|
||||
if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then
|
||||
echo "Mounted /dev/mapper/$mapping to $mount"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0
|
||||
fi
|
||||
|
||||
if ! pgrep -f decryption-webserver >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 decryption-webserver &
|
||||
fi
|
||||
|
||||
if ! pgrep wpa_supplicant >/dev/null 2>&1; then
|
||||
>/tmp/wpa_supplicant.conf cat <<EOF
|
||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||
update_config=1
|
||||
ap_scan=2
|
||||
|
||||
network={
|
||||
ssid="DECRYPT-ME"
|
||||
mode=2
|
||||
key_mgmt=WPA-PSK
|
||||
psk="pwnagotchi"
|
||||
frequency=2437
|
||||
}
|
||||
EOF
|
||||
>/dev/null 2>&1 wpa_supplicant -u -s -O -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf &
|
||||
fi
|
||||
|
||||
if ! pgrep dnsmasq >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h &
|
||||
fi
|
||||
|
||||
return 1
|
||||
fi
|
||||
done </root/.pwnagotchi-crypted
|
||||
|
||||
# overwrite passwords
|
||||
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/dev/null
|
||||
# delete
|
||||
rm /tmp/.pwnagotchi-secret-*
|
||||
sync # flush
|
||||
|
||||
pkill wpa_supplicant
|
||||
pkill dnsmasq
|
||||
pid="$(pgrep -f "decryption-webserver")"
|
||||
[[ -n "$pid" ]] && kill "$pid"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
@ -3,107 +3,46 @@
|
||||
{
|
||||
"name": "pwnagotchi",
|
||||
"type": "arm-image",
|
||||
"iso_url": "https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-07-12/2019-07-10-raspbian-buster-lite.zip",
|
||||
"iso_checksum_type": "sha256",
|
||||
"iso_checksum": "9e5cf24ce483bb96e7736ea75ca422e3560e7b455eee63dd28f66fa1825db70e",
|
||||
"last_partition_extra_size": 3221225472
|
||||
"iso_url": "https://downloads.raspberrypi.org/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-05-03/2023-05-03-raspios-buster-armhf-lite.img.xz",
|
||||
"iso_checksum": "3d210e61b057de4de90eadb46e28837585a9b24247c221998f5bead04f88624c",
|
||||
"target_image_size": 9368709120,
|
||||
"qemu_args": ["-cpu", "arm1176"]
|
||||
}
|
||||
],
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"sed -i 's/^\\([^#]\\)/#\\1/g' /etc/ld.so.preload",
|
||||
"mv /etc/ld.so.preload /etc/ld.so.preload.DISABLED",
|
||||
"uname -a",
|
||||
"dpkg-architecture",
|
||||
"apt-get -y update",
|
||||
"apt-get install -y ansible"
|
||||
"mkdir -p /usr/local/src/pwnagotchi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/pwnlib",
|
||||
"destination": "/usr/bin/pwnlib"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/bettercap-launcher",
|
||||
"destination": "/usr/bin/bettercap-launcher"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/pwnagotchi-launcher",
|
||||
"destination": "/usr/bin/pwnagotchi-launcher"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/monstop",
|
||||
"destination": "/usr/bin/monstop"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/monstart",
|
||||
"destination": "/usr/bin/monstart"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/hdmion",
|
||||
"destination": "/usr/bin/hdmion"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/hdmioff",
|
||||
"destination": "/usr/bin/hdmioff"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/lo-cfg",
|
||||
"destination": "/etc/network/interfaces.d/lo-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/wlan0-cfg",
|
||||
"destination": "/etc/network/interfaces.d/wlan0-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/usb0-cfg",
|
||||
"destination": "/etc/network/interfaces.d/usb0-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/eth0-cfg",
|
||||
"destination": "/etc/network/interfaces.d/eth0-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/systemd/system/pwngrid-peer.service",
|
||||
"destination": "/etc/systemd/system/pwngrid-peer.service"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/systemd/system/pwnagotchi.service",
|
||||
"destination": "/etc/systemd/system/pwnagotchi.service"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/systemd/system/bettercap.service",
|
||||
"destination": "/etc/systemd/system/bettercap.service"
|
||||
"sources": [
|
||||
"../dist/pwnagotchi-{{user `pwn_version`}}.tar.gz"
|
||||
],
|
||||
"destination": "/usr/local/src/pwnagotchi/"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"chmod +x /usr/bin/*"
|
||||
"apt-get -y --allow-releaseinfo-change update",
|
||||
"apt-get install -y --no-install-recommends ansible"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "ansible-local",
|
||||
"playbook_file": "pwnagotchi.yml",
|
||||
"extra_arguments": [ "--extra-vars \"ansible_python_interpreter=/usr/bin/python3\"" ],
|
||||
"command": "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION={{user `pwn_version`}} PWN_HOSTNAME={{user `pwn_hostname`}} ansible-playbook"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"sed -i 's/^#\\(.+\\)/\\1/g' /etc/ld.so.preload"
|
||||
"mv /etc/ld.so.preload.DISABLED /etc/ld.so.preload"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
- hosts:
|
||||
- 127.0.0.1
|
||||
gather_facts: yes
|
||||
become: yes
|
||||
vars:
|
||||
pwnagotchi:
|
||||
@ -10,6 +11,7 @@
|
||||
boot_options:
|
||||
- "dtoverlay=dwc2"
|
||||
- "dtoverlay=spi1-3cs"
|
||||
- "dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4"
|
||||
- "dtparam=spi=on"
|
||||
- "dtparam=i2c_arm=on"
|
||||
- "dtparam=i2c1=on"
|
||||
@ -33,12 +35,14 @@
|
||||
- bluetooth.service
|
||||
- triggerhappy.service
|
||||
- ifup@wlan0.service
|
||||
- dnsmasq.service
|
||||
packages:
|
||||
bettercap:
|
||||
url: "https://github.com/bettercap/bettercap/releases/download/v2.26.1/bettercap_linux_armhf_v2.26.1.zip"
|
||||
# We will install bettercap from source
|
||||
# url: "https://github.com/bettercap/bettercap/releases/download/v2.31.0/bettercap_linux_armhf_v2.31.0.zip"
|
||||
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
||||
pwngrid:
|
||||
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.1/pwngrid_linux_armhf_v1.10.1.zip"
|
||||
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.3/pwngrid_linux_armhf_v1.10.3.zip"
|
||||
apt:
|
||||
hold:
|
||||
- firmware-atheros
|
||||
@ -47,15 +51,18 @@
|
||||
- firmware-misc-nonfree
|
||||
- firmware-realtek
|
||||
remove:
|
||||
- rasberrypi-net-mods
|
||||
- raspberrypi-net-mods
|
||||
- dhcpcd5
|
||||
- triggerhappy
|
||||
- wpa_supplicant
|
||||
- nfs-common
|
||||
# Remove every golang package because we will install go-1.20.2
|
||||
- golang*
|
||||
- python2*
|
||||
install:
|
||||
- rsync
|
||||
- vim
|
||||
- screen
|
||||
- golang
|
||||
- git
|
||||
- build-essential
|
||||
- python3-pip
|
||||
@ -66,6 +73,7 @@
|
||||
- libopenmpi-dev
|
||||
- libatlas-base-dev
|
||||
- libjasper-dev
|
||||
- libgtk-3-0
|
||||
- libqtgui4
|
||||
- libqt4-test
|
||||
- libopenjp2-7
|
||||
@ -83,10 +91,6 @@
|
||||
- libnetfilter-queue-dev
|
||||
- libopenmpi3
|
||||
- dphys-swapfile
|
||||
- kalipi-kernel
|
||||
- kalipi-bootloader
|
||||
- kalipi-re4son-firmware
|
||||
- kalipi-kernel-headers
|
||||
- libraspberrypi0
|
||||
- libraspberrypi-dev
|
||||
- libraspberrypi-doc
|
||||
@ -100,11 +104,36 @@
|
||||
- bc
|
||||
- fonts-freefont-ttf
|
||||
- fbi
|
||||
- python3-flask
|
||||
- python3-flask-cors
|
||||
- python3-flaskext.wtf
|
||||
- fonts-ipaexfont-gothic
|
||||
- cryptsetup
|
||||
- dnsmasq
|
||||
- aircrack-ng
|
||||
- raspberrypi-kernel-headers
|
||||
- libgmp3-dev
|
||||
- qpdf
|
||||
- bison
|
||||
- flex
|
||||
- make
|
||||
- autoconf
|
||||
- libtool
|
||||
- texinfo
|
||||
- binutils
|
||||
- lnav
|
||||
- p7zip-full
|
||||
|
||||
environment:
|
||||
ARCHFLAGS: "-arch armv7l"
|
||||
|
||||
tasks:
|
||||
- name: System details
|
||||
debug:
|
||||
msg="{{ item }}"
|
||||
with_items:
|
||||
- "{{ ansible_distribution }}"
|
||||
- "{{ ansible_distribution_version }}"
|
||||
- "{{ ansible_distribution_major_version }}"
|
||||
- "{{ ansible_architecture }}"
|
||||
- "{{ ansible_machine }}"
|
||||
- name: change hostname
|
||||
hostname:
|
||||
name: "{{pwnagotchi.hostname}}"
|
||||
@ -126,16 +155,6 @@
|
||||
line: 'ExecStart=/usr/lib/bluetooth/bluetoothd --noplugin=sap'
|
||||
state: present
|
||||
|
||||
- name: Add re4son-kernel repo key
|
||||
apt_key:
|
||||
url: https://re4son-kernel.com/keys/http/archive-key.asc
|
||||
state: present
|
||||
|
||||
- name: Add re4son-kernel repository
|
||||
apt_repository:
|
||||
repo: deb http://http.re4son-kernel.com/re4son/ kali-pi main
|
||||
state: present
|
||||
|
||||
- name: add firmware packages to hold
|
||||
dpkg_selections:
|
||||
name: "{{ item }}"
|
||||
@ -161,23 +180,35 @@
|
||||
name: "{{ packages.apt.install }}"
|
||||
state: present
|
||||
|
||||
- name: Update .bashrc (root)
|
||||
blockinfile:
|
||||
dest: /root/.bashrc
|
||||
state: present
|
||||
block: |
|
||||
export MAKEFLAGS=-j$(nproc)
|
||||
insertafter: EOF
|
||||
|
||||
- name: configure dphys-swapfile
|
||||
file:
|
||||
lineinfile:
|
||||
path: /etc/dphys-swapfile
|
||||
content: "CONF_SWAPSIZE=1024"
|
||||
regexp: "^CONF_SWAPSIZE=.*$"
|
||||
line: "CONF_SWAPSIZE=512"
|
||||
|
||||
- name: clone papirus repository
|
||||
git:
|
||||
repo: https://github.com/repaper/gratis.git
|
||||
dest: /usr/local/src/gratis
|
||||
retries: 5000
|
||||
delay: 5
|
||||
register: gratisgit
|
||||
until: gratisgit is succeeded
|
||||
|
||||
- name: build papirus service
|
||||
make:
|
||||
chdir: /usr/local/src/gratis
|
||||
target: rpi
|
||||
params:
|
||||
EPD_IO: epd_io.h
|
||||
EPD_IO: epd_io_free_uart.h
|
||||
PANEL_VERSION: 'V231_G2'
|
||||
when: gratisgit.changed
|
||||
|
||||
@ -186,7 +217,7 @@
|
||||
chdir: /usr/local/src/gratis
|
||||
target: rpi-install
|
||||
params:
|
||||
EPD_IO: epd_io.h
|
||||
EPD_IO: epd_io_free_uart.h
|
||||
PANEL_VERSION: 'V231_G2'
|
||||
when: gratisgit.changed
|
||||
|
||||
@ -196,57 +227,58 @@
|
||||
regexp: "#EPD_SIZE=2.0"
|
||||
line: "EPD_SIZE=2.0"
|
||||
|
||||
- name: collect python pip package list
|
||||
command: "pip3 list"
|
||||
register: pip_output
|
||||
- name: Delete papirus content & directory
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/gratis
|
||||
when: gratisgit.changed
|
||||
|
||||
- name: set python pip package facts
|
||||
set_fact:
|
||||
pip_packages: >
|
||||
{{ pip_packages | default({}) | combine( { item.split()[0]: item.split()[1] } ) }}
|
||||
with_items: "{{ pip_output.stdout_lines }}"
|
||||
|
||||
- name: acquire python3 pip target
|
||||
command: "python3 -c 'import sys;print(sys.path.pop())'"
|
||||
register: pip_target
|
||||
|
||||
- name: clone pwnagotchi repository
|
||||
git:
|
||||
repo: https://github.com/evilsocket/pwnagotchi.git
|
||||
dest: /usr/local/src/pwnagotchi
|
||||
register: pwnagotchigit
|
||||
|
||||
- name: fetch pwnagotchi version
|
||||
set_fact:
|
||||
pwnagotchi_version: "{{ lookup('file', '/usr/local/src/pwnagotchi/pwnagotchi/__init__.py') | replace('\n', ' ') | regex_replace('.*version.*=.*''([0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*)''.*', '\\1') }}"
|
||||
|
||||
- name: pwnagotchi version found
|
||||
debug:
|
||||
msg: "{{ pwnagotchi_version }}"
|
||||
|
||||
- name: build pwnagotchi wheel
|
||||
command: "python3 setup.py sdist bdist_wheel"
|
||||
# pip v20.3 uses a newer dependency resolver that better handles our unique situation.
|
||||
# Specifically, it handles mismatches between direct requirements without extras and
|
||||
# indirect requirements that do want extras (e.g. gym vs stable-baselines->gym[atari]).
|
||||
- name: Upgrade pip and install rpi-hardware-pwm
|
||||
shell: "python3 -m pip install pip>=20.3 rpi-hardware-pwm --verbose --retries 5000"
|
||||
args:
|
||||
chdir: /usr/local/src/pwnagotchi
|
||||
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi.version)
|
||||
executable: /bin/bash
|
||||
|
||||
- name: install opencv-python
|
||||
pip:
|
||||
name: "https://www.piwheels.hostedpi.com/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl"
|
||||
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
|
||||
when: (pip_packages['opencv-python'] is undefined) or (pip_packages['opencv-python'] != '3.4.3.18')
|
||||
# We need the --ignore-installed option so that pip simply overwrites/upgrades existing
|
||||
# packages instead of trying to uninstall them first. While this sounds dangerous,
|
||||
# this matches the legacy behavior of pip. This is required to prevent pip from trying
|
||||
# (and failing) to uninstall python packages that were originally installed via apt.
|
||||
- name: Install pwnagotchi from source archive
|
||||
shell: "python3 -m pip install /usr/local/src/pwnagotchi/pwnagotchi-{{ pwnagotchi.version }}.tar.gz --verbose --ignore-installed --retries 5000"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
|
||||
- name: install tensorflow
|
||||
pip:
|
||||
name: "https://www.piwheels.hostedpi.com/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl"
|
||||
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
|
||||
when: (pip_packages['tensorflow'] is undefined) or (pip_packages['tensorflow'] != '1.13.1')
|
||||
- name: create custom plugin directory
|
||||
file:
|
||||
path: /usr/local/share/pwnagotchi/custom-plugins/
|
||||
state: directory
|
||||
|
||||
- name: install pwnagotchi wheel and dependencies
|
||||
pip:
|
||||
name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
|
||||
extra_args: "--no-cache-dir"
|
||||
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi.version)
|
||||
- name: clone pwnagotchi plugins repository
|
||||
git:
|
||||
repo: https://git.chadwaltercummings.me/scifijunkie/pwnagotchi-plugins-contrib.git
|
||||
dest: /usr/local/share/pwnagotchi/available-plugins
|
||||
retries: 5000
|
||||
delay: 5
|
||||
register: pwnagotchipluginsgit
|
||||
until: pwnagotchipluginsgit is succeeded
|
||||
|
||||
- name: Copy aircrackonly.py
|
||||
copy:
|
||||
src: /usr/local/share/pwnagotchi/available-plugins/aircrackonly.py
|
||||
dest: /usr/local/share/pwnagotchi/custom-plugins/aircrackonly.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: '644'
|
||||
|
||||
- name: Copy handshakes-dl.py
|
||||
copy:
|
||||
src: /usr/local/share/pwnagotchi/available-plugins/handshakes-dl.py
|
||||
dest: /usr/local/share/pwnagotchi/custom-plugins/handshakes-dl.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: '644'
|
||||
|
||||
- name: download and install pwngrid
|
||||
unarchive:
|
||||
@ -255,21 +287,41 @@
|
||||
remote_src: yes
|
||||
mode: 0755
|
||||
|
||||
- name: download and install bettercap
|
||||
- name: Install go-1.24.1
|
||||
unarchive:
|
||||
src: "{{ packages.bettercap.url }}"
|
||||
dest: /usr/bin
|
||||
src: https://go.dev/dl/go1.24.1.linux-armv6l.tar.gz
|
||||
dest: /usr/local
|
||||
remote_src: yes
|
||||
exclude:
|
||||
- README.md
|
||||
- LICENSE.md
|
||||
mode: 0755
|
||||
register: golang
|
||||
|
||||
- name: Update .bashrc for go-1.24.1 (pi)
|
||||
blockinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
state: present
|
||||
block: |
|
||||
export GOPATH=$HOME/go
|
||||
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
||||
insertafter: EOF
|
||||
when: golang.changed
|
||||
|
||||
- name: Install bettercap
|
||||
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && git clone https://github.com/bettercap/bettercap.git && cd bettercap/ && make build && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: bettercap
|
||||
|
||||
- name: Link bettercap
|
||||
command: ln -s /usr/local/bin/bettercap /usr/bin/bettercap
|
||||
when: bettercap.changed
|
||||
|
||||
- name: clone bettercap caplets
|
||||
git:
|
||||
repo: https://github.com/bettercap/caplets.git
|
||||
dest: /tmp/caplets
|
||||
retries: 5000
|
||||
delay: 5
|
||||
register: capletsgit
|
||||
until: capletsgit is succeeded
|
||||
|
||||
- name: install bettercap caplets
|
||||
make:
|
||||
@ -284,6 +336,227 @@
|
||||
remote_src: yes
|
||||
mode: 0755
|
||||
|
||||
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
||||
- name: clone nexmon repository
|
||||
git:
|
||||
repo: https://github.com/seemoo-lab/nexmon.git
|
||||
dest: /usr/local/src/nexmon
|
||||
# version: bfb3fe90c881498d7ee245b38f16722c1de26fa1
|
||||
retries: 5000
|
||||
delay: 5
|
||||
register: nexmongit
|
||||
until: nexmongit is succeeded
|
||||
|
||||
- name: configure libisl
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/isl-0.10/ ./configure
|
||||
|
||||
- name: make libisl
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/isl-0.10/ make
|
||||
|
||||
- name: install libisl
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/isl-0.10/ make install
|
||||
|
||||
- name: link libisl
|
||||
command: ln -s /usr/local/lib/libisl.so /usr/lib/arm-linux-gnueabihf/libisl.so.10
|
||||
|
||||
- name: autoreconf libmpfr
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ autoreconf -f -i
|
||||
|
||||
- name: configure libmpfr
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ ./configure
|
||||
|
||||
- name: make libmpfr
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ make
|
||||
|
||||
- name: install libmpfr
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ make install
|
||||
|
||||
- name: link libmpfr
|
||||
command: ln -s /usr/local/lib/libmpfr.so /usr/lib/arm-linux-gnueabihf/libmpfr.so.4
|
||||
|
||||
- name: make firmware
|
||||
shell: "source ./setup_env.sh && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
|
||||
- name: choose the right kernel version (bcm43436b0)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "KERNEL_VERSION = .*$"
|
||||
replace: "KERNEL_VERSION = 5.10"
|
||||
|
||||
- name: choose the right kernel release (variable) (bcm43436b0)
|
||||
lineinfile:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/Makefile
|
||||
insertafter: "DRIVER_FOLDER_NAME = .*$"
|
||||
line: "KERNEL_RELEASE = 5.10.103-v7+"
|
||||
|
||||
- name: choose the right kernel release (replace string) (bcm43436b0)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "shell uname -r"
|
||||
replace: "KERNEL_RELEASE"
|
||||
|
||||
- name: make firmware patch (bcm43436b0)
|
||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: backup original firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make backup-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: install new firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make install-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
- name: install new firmware (bcm43436b0)
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/brcmfmac43436-sdio.bin
|
||||
dest: /lib/firmware/brcm/brcmfmac43436-sdio.bin
|
||||
|
||||
- name: choose the right kernel version (bcm43430a1)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "KERNEL_VERSION = .*$"
|
||||
replace: "KERNEL_VERSION = 5.10"
|
||||
|
||||
- name: choose the right kernel release (variable) (bcm43430a1)
|
||||
lineinfile:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/Makefile
|
||||
insertafter: "DRIVER_FOLDER_NAME = .*$"
|
||||
line: "KERNEL_RELEASE = 5.10.103-v7+"
|
||||
|
||||
- name: choose the right kernel release (replace string) (bcm43430a1)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "shell uname -r"
|
||||
replace: "KERNEL_RELEASE"
|
||||
|
||||
- name: make firmware patch (bcm43430a1)
|
||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: backup original firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make backup-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: install new firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make install-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
- name: install new firmware (bcm43430a1)
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/brcmfmac43430-sdio.bin
|
||||
dest: /lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||
|
||||
- name: Delete the firmware blob to avoid it crashing
|
||||
file:
|
||||
state: absent
|
||||
path: /lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
|
||||
|
||||
- name: Delete the RPiZW firmware blob to avoid it crashing
|
||||
file:
|
||||
state: absent
|
||||
path: /lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
|
||||
|
||||
- name: Delete the RPi3 firmware blob to avoid it crashing
|
||||
file:
|
||||
state: absent
|
||||
path: /lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob
|
||||
|
||||
- name: choose the right kernel version (bcm43455c0)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "KERNEL_VERSION = .*$"
|
||||
replace: "KERNEL_VERSION = 5.10"
|
||||
|
||||
- name: choose the right kernel release (variable) (bcm43455c0)
|
||||
lineinfile:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/Makefile
|
||||
insertafter: "DRIVER_FOLDER_NAME = .*$"
|
||||
line: "KERNEL_RELEASE = 5.10.103-v7+"
|
||||
|
||||
- name: choose the right kernel release (replace string) (bcm43455c0)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "shell uname -r"
|
||||
replace: "KERNEL_RELEASE"
|
||||
|
||||
- name: make firmware patch (bcm43455c0)
|
||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: backup original firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make backup-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: install new firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make install-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
- name: install new firmware (bcm43455c0)
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/brcmfmac43455-sdio.bin
|
||||
dest: /lib/firmware/brcm/brcmfmac43455-sdio.bin
|
||||
|
||||
- name: make nexutil
|
||||
command: chdir=/usr/local/src/nexmon/utilities/nexutil/ make
|
||||
|
||||
- name: make install nexutil
|
||||
command: chdir=/usr/local/src/nexmon/utilities/nexutil/ make install
|
||||
|
||||
# - name: copy modified driver
|
||||
# shell: "cd /usr/local/src/nexmon/patches/driver/brcmfmac_5.10.y-nexmon/ && cp brcmfmac.ko /lib/modules/5.10.103-v7+/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko && depmod -a"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
|
||||
- name: copy modified driver (everyone but RPiZW)
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/driver/brcmfmac_5.10.y-nexmon/brcmfmac.ko
|
||||
dest: /lib/modules/5.10.103-v7+/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko
|
||||
|
||||
- name: ensure depmod runs on reboot to load modified driver (brcmfmac)
|
||||
lineinfile:
|
||||
dest: /etc/rc.local
|
||||
line: "/sbin/depmod -a"
|
||||
|
||||
# To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation
|
||||
- name: Delete nexmon content & directory
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/nexmon/
|
||||
|
||||
- name: Add pwnlog alias
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnlog='tail -f -n300 /var/log/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
|
||||
insertafter: EOF
|
||||
|
||||
- name: add HDMI powersave to rc.local
|
||||
blockinfile:
|
||||
path: /etc/rc.local
|
||||
@ -300,23 +573,43 @@
|
||||
|
||||
- name: check if user configuration exists
|
||||
stat:
|
||||
path: /etc/pwnagotchi/config.yml
|
||||
path: /etc/pwnagotchi/config.toml
|
||||
register: user_config
|
||||
|
||||
- name: create /etc/pwnagotchi/config.yml
|
||||
- name: create /etc/pwnagotchi/config.toml
|
||||
copy:
|
||||
dest: /etc/pwnagotchi/config.yml
|
||||
dest: /etc/pwnagotchi/config.toml
|
||||
content: |
|
||||
# Add your configuration overrides on this file any configuration changes done to default.yml will be lost!
|
||||
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||
# Example:
|
||||
#
|
||||
# ui:
|
||||
# display:
|
||||
# type: 'inkyphat'
|
||||
# color: 'black'
|
||||
#
|
||||
# ui.display.enabled = true
|
||||
# ui.display.type = "waveshare_2"
|
||||
when: not user_config.stat.exists
|
||||
|
||||
# - name: append commented out parameters for usb_hat_c.py
|
||||
# lineinfile:
|
||||
# dest: /etc/pwnagotchi/config.toml
|
||||
# line: "# main.plugins.ups_hat_c.enabled = true\n# main.plugins.ups_hat_c.label_on = true # show BAT label or just percentage\n# main.plugins.ups_hat_c.shutdown = 5 # battery percent at which the device will turn off\n# main.plugins.ups_hat_c.bat_x_coord = 140\n# main.plugins.ups_hat_c.bat_y_coord = 0"
|
||||
# insertafter: EOF
|
||||
|
||||
#bizzarely changing the plugin code directly reverts to the old string
|
||||
- name: Reconfigure auto-update to point to the scifijunk repo
|
||||
replace:
|
||||
dest: /usr/local/lib/python3.7/dist-packages/pwnagotchi/plugins/default/auto-update.py
|
||||
backup: no
|
||||
regexp: "evilsocket/pwnagotchi"
|
||||
replace: "scifijunk/pwnagotchi"
|
||||
|
||||
- name: Delete unnecessary large folder to save space (/root/go)
|
||||
file:
|
||||
state: absent
|
||||
path: /root/go
|
||||
|
||||
- name: Delete unnecessary large folder to save space (/root/.cache)
|
||||
file:
|
||||
state: absent
|
||||
path: /root/.cache
|
||||
|
||||
- name: enable ssh on boot
|
||||
file:
|
||||
path: /boot/ssh
|
||||
@ -361,15 +654,15 @@
|
||||
Hi! I'm a pwnagotchi, please take good care of me!
|
||||
Here are some basic things you need to know to raise me properly!
|
||||
|
||||
If you want to change my configuration, use /etc/pwnagotchi/config.yml
|
||||
If you want to change my configuration, use /etc/pwnagotchi/config.toml
|
||||
|
||||
All the configuration options can be found on /etc/pwnagotchi/default.yml,
|
||||
All the configuration options can be found on /etc/pwnagotchi/default.toml,
|
||||
but don't change this file because I will recreate it every time I'm restarted!
|
||||
|
||||
I'm managed by systemd. Here are some basic commands.
|
||||
|
||||
If you want to know what I'm doing, you can check my logs with the command
|
||||
journalctl -fu pwnagotchi
|
||||
tail -f /var/log/pwnagotchi.log
|
||||
|
||||
If you want to know if I'm running, you can use
|
||||
systemctl status pwnagotchi
|
||||
@ -384,9 +677,14 @@
|
||||
You learn more about me at https://pwnagotchi.ai/
|
||||
when: hostname.changed
|
||||
|
||||
# Ansible's apt module has an "autoclean" option but it only removes packages
|
||||
# that can no longer be downloaded. Ansible v2.13 added the "clean" option
|
||||
# which actually purges the apt cache, but that's newer than what we can
|
||||
# install from the RasPiOS repos. Instead, we'll manually clean the cache.
|
||||
- name: clean apt cache
|
||||
apt:
|
||||
autoclean: yes
|
||||
command: "apt-get clean"
|
||||
args:
|
||||
warn: false
|
||||
|
||||
- name: remove dependencies that are no longer required
|
||||
apt:
|
||||
|
@ -3,12 +3,12 @@ import logging
|
||||
import time
|
||||
import re
|
||||
|
||||
import pwnagotchi.ui.view as view
|
||||
import pwnagotchi
|
||||
|
||||
version = '1.4.2'
|
||||
|
||||
from pwnagotchi._version import __version__
|
||||
|
||||
_name = None
|
||||
config = None
|
||||
|
||||
|
||||
def set_name(new_name):
|
||||
@ -65,8 +65,6 @@ def mem_usage():
|
||||
kb_mem_total = int(line.split()[1])
|
||||
if line.startswith("MemFree:"):
|
||||
kb_mem_free = int(line.split()[1])
|
||||
if line.startswith("MemAvailable:"):
|
||||
kb_mem_available = int(line.split()[1])
|
||||
if line.startswith("Buffers:"):
|
||||
kb_main_buffers = int(line.split()[1])
|
||||
if line.startswith("Cached:"):
|
||||
@ -77,18 +75,27 @@ def mem_usage():
|
||||
return 0
|
||||
|
||||
|
||||
def cpu_load():
|
||||
def _cpu_stat():
|
||||
"""
|
||||
Returns the splitted first line of the /proc/stat file
|
||||
"""
|
||||
with open('/proc/stat', 'rt') as fp:
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if line.startswith('cpu '):
|
||||
parts = list(map(int, line.split()[1:]))
|
||||
user_n = parts[0]
|
||||
sys_n = parts[2]
|
||||
idle_n = parts[3]
|
||||
tot = user_n + sys_n + idle_n
|
||||
return (user_n + sys_n) / tot
|
||||
return 0
|
||||
return list(map(int,fp.readline().split()[1:]))
|
||||
|
||||
|
||||
def cpu_load():
|
||||
"""
|
||||
Returns the current cpuload
|
||||
"""
|
||||
parts0 = _cpu_stat()
|
||||
time.sleep(0.1)
|
||||
parts1 = _cpu_stat()
|
||||
parts_diff = [p1 - p0 for (p0, p1) in zip(parts0, parts1)]
|
||||
user, nice, sys, idle, iowait, irq, softirq, steal, _guest, _guest_nice = parts_diff
|
||||
idle_sum = idle + iowait
|
||||
non_idle_sum = user + nice + sys + irq + softirq + steal
|
||||
total = idle_sum + non_idle_sum
|
||||
return non_idle_sum / total
|
||||
|
||||
|
||||
def temperature(celsius=True):
|
||||
@ -100,10 +107,19 @@ def temperature(celsius=True):
|
||||
|
||||
def shutdown():
|
||||
logging.warning("shutting down ...")
|
||||
|
||||
from pwnagotchi.ui import view
|
||||
if view.ROOT:
|
||||
view.ROOT.on_shutdown()
|
||||
# give it some time to refresh the ui
|
||||
time.sleep(10)
|
||||
|
||||
logging.warning("syncing...")
|
||||
|
||||
from pwnagotchi import fs
|
||||
for m in fs.mounts:
|
||||
m.sync()
|
||||
|
||||
os.system("sync")
|
||||
os.system("halt")
|
||||
|
||||
@ -117,6 +133,7 @@ def restart(mode):
|
||||
os.system("touch /root/.pwnagotchi-manual")
|
||||
|
||||
os.system("service bettercap restart")
|
||||
time.sleep(2)
|
||||
os.system("service pwnagotchi restart")
|
||||
|
||||
|
||||
@ -127,6 +144,7 @@ def reboot(mode=None):
|
||||
else:
|
||||
logging.warning("rebooting ...")
|
||||
|
||||
from pwnagotchi.ui import view
|
||||
if view.ROOT:
|
||||
view.ROOT.on_rebooting()
|
||||
# give it some time to refresh the ui
|
||||
@ -137,5 +155,11 @@ def reboot(mode=None):
|
||||
elif mode == 'MANU':
|
||||
os.system("touch /root/.pwnagotchi-manual")
|
||||
|
||||
logging.warning("syncing...")
|
||||
|
||||
from pwnagotchi import fs
|
||||
for m in fs.mounts:
|
||||
m.sync()
|
||||
|
||||
os.system("sync")
|
||||
os.system("shutdown -r now")
|
||||
|
1
pwnagotchi/_version.py
Normal file
1
pwnagotchi/_version.py
Normal file
@ -0,0 +1 @@
|
||||
__version__='1.10.0'
|
@ -3,6 +3,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
import asyncio
|
||||
import _thread
|
||||
|
||||
import pwnagotchi
|
||||
@ -30,7 +31,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
AsyncTrainer.__init__(self, config)
|
||||
|
||||
self._started_at = time.time()
|
||||
self._filter = None if config['main']['filter'] is None else re.compile(config['main']['filter'])
|
||||
self._filter = None if not config['main']['filter'] else re.compile(config['main']['filter'])
|
||||
self._current_channel = 0
|
||||
self._tot_aps = 0
|
||||
self._aps_on_channel = 0
|
||||
@ -49,7 +50,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
if not os.path.exists(config['bettercap']['handshakes']):
|
||||
os.makedirs(config['bettercap']['handshakes'])
|
||||
|
||||
logging.info("%s@%s (v%s)", pwnagotchi.name(), self.fingerprint(), pwnagotchi.version)
|
||||
logging.info("%s@%s (v%s)", pwnagotchi.name(), self.fingerprint(), pwnagotchi.__version__)
|
||||
for _, plugin in plugins.loaded.items():
|
||||
logging.debug("plugin '%s' v%s", plugin.__class__.__name__, plugin.__version__)
|
||||
|
||||
@ -68,7 +69,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
for tag in self._config['bettercap']['silence']:
|
||||
try:
|
||||
self.run('events.ignore %s' % tag, verbose_errors=False)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _reset_wifi_settings(self):
|
||||
@ -121,9 +122,9 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
def _wait_bettercap(self):
|
||||
while True:
|
||||
try:
|
||||
s = self.session()
|
||||
_s = self.session()
|
||||
return
|
||||
except:
|
||||
except Exception:
|
||||
logging.info("waiting for bettercap API to be available ...")
|
||||
time.sleep(1)
|
||||
|
||||
@ -134,6 +135,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
self.set_starting()
|
||||
self.start_monitor_mode()
|
||||
self.start_event_polling()
|
||||
self.start_session_fetcher()
|
||||
# print initial stats
|
||||
self.next_epoch()
|
||||
self.set_ready()
|
||||
@ -158,7 +160,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
try:
|
||||
self.run('wifi.recon.channel %s' % ','.join(map(str, channels)))
|
||||
except Exception as e:
|
||||
logging.exception("error")
|
||||
logging.exception("Error while setting wifi.recon.channels (%s)", e)
|
||||
|
||||
self.wait_for(recon_time, sleeping=False)
|
||||
|
||||
@ -188,7 +190,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
if self._filter_included(ap):
|
||||
aps.append(ap)
|
||||
except Exception as e:
|
||||
logging.exception("error")
|
||||
logging.exception("Error while getting acces points (%s)", e)
|
||||
|
||||
aps.sort(key=lambda ap: ap['channel'])
|
||||
return self.set_access_points(aps)
|
||||
@ -303,60 +305,74 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
if not no_exceptions:
|
||||
raise
|
||||
|
||||
def _event_poller(self):
|
||||
self._load_recovery_data()
|
||||
|
||||
def start_session_fetcher(self):
|
||||
_thread.start_new_thread(self._fetch_stats, ())
|
||||
|
||||
|
||||
def _fetch_stats(self):
|
||||
while True:
|
||||
s = self.session()
|
||||
self._update_uptime(s)
|
||||
self._update_advertisement(s)
|
||||
self._update_peers()
|
||||
self._update_counters()
|
||||
self._update_handshakes(0)
|
||||
time.sleep(1)
|
||||
|
||||
async def _on_event(self, msg):
|
||||
found_handshake = False
|
||||
jmsg = json.loads(msg)
|
||||
|
||||
# give plugins access to all raw bettercap events
|
||||
try:
|
||||
plugins.on('bcap_%s' % re.sub(r"[^a-z0-9_]+", "_", jmsg['tag'].lower()), self, jmsg)
|
||||
except Exception as err:
|
||||
logging.error("Processing event: %s" % err)
|
||||
|
||||
if jmsg['tag'] == 'wifi.client.handshake':
|
||||
filename = jmsg['data']['file']
|
||||
sta_mac = jmsg['data']['station']
|
||||
ap_mac = jmsg['data']['ap']
|
||||
key = "%s -> %s" % (sta_mac, ap_mac)
|
||||
if key not in self._handshakes:
|
||||
self._handshakes[key] = jmsg
|
||||
s = self.session()
|
||||
ap_and_station = self._find_ap_sta_in(sta_mac, ap_mac, s)
|
||||
if ap_and_station is None:
|
||||
logging.warning("!!! captured new handshake: %s !!!", key)
|
||||
self._last_pwnd = ap_mac
|
||||
plugins.on('handshake', self, filename, ap_mac, sta_mac)
|
||||
else:
|
||||
(ap, sta) = ap_and_station
|
||||
self._last_pwnd = ap['hostname'] if ap['hostname'] != '' and ap[
|
||||
'hostname'] != '<hidden>' else ap_mac
|
||||
logging.warning(
|
||||
"!!! captured new handshake on channel %d, %d dBm: %s (%s) -> %s [%s (%s)] !!!",
|
||||
ap['channel'],
|
||||
ap['rssi'],
|
||||
sta['mac'], sta['vendor'],
|
||||
ap['hostname'], ap['mac'], ap['vendor'])
|
||||
plugins.on('handshake', self, filename, ap, sta)
|
||||
found_handshake = True
|
||||
self._update_handshakes(1 if found_handshake else 0)
|
||||
|
||||
def _event_poller(self, loop):
|
||||
self._load_recovery_data()
|
||||
self.run('events.clear')
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
new_shakes = 0
|
||||
|
||||
logging.debug("polling events ...")
|
||||
|
||||
try:
|
||||
s = self.session()
|
||||
self._update_uptime(s)
|
||||
|
||||
self._update_advertisement(s)
|
||||
self._update_peers()
|
||||
self._update_counters()
|
||||
|
||||
for h in [e for e in self.events() if e['tag'] == 'wifi.client.handshake']:
|
||||
filename = h['data']['file']
|
||||
sta_mac = h['data']['station']
|
||||
ap_mac = h['data']['ap']
|
||||
key = "%s -> %s" % (sta_mac, ap_mac)
|
||||
|
||||
if key not in self._handshakes:
|
||||
self._handshakes[key] = h
|
||||
new_shakes += 1
|
||||
ap_and_station = self._find_ap_sta_in(sta_mac, ap_mac, s)
|
||||
if ap_and_station is None:
|
||||
logging.warning("!!! captured new handshake: %s !!!", key)
|
||||
self._last_pwnd = ap_mac
|
||||
plugins.on('handshake', self, filename, ap_mac, sta_mac)
|
||||
else:
|
||||
(ap, sta) = ap_and_station
|
||||
self._last_pwnd = ap['hostname'] if ap['hostname'] != '' and ap[
|
||||
'hostname'] != '<hidden>' else ap_mac
|
||||
logging.warning(
|
||||
"!!! captured new handshake on channel %d, %d dBm: %s (%s) -> %s [%s (%s)] !!!",
|
||||
ap['channel'],
|
||||
ap['rssi'],
|
||||
sta['mac'], sta['vendor'],
|
||||
ap['hostname'], ap['mac'], ap['vendor'])
|
||||
plugins.on('handshake', self, filename, ap, sta)
|
||||
|
||||
except Exception as e:
|
||||
logging.error("error: %s", e)
|
||||
|
||||
finally:
|
||||
self._update_handshakes(new_shakes)
|
||||
loop.create_task(self.start_websocket(self._on_event))
|
||||
loop.run_forever()
|
||||
except Exception as ex:
|
||||
logging.debug("Error while polling via websocket (%s)", ex)
|
||||
|
||||
def start_event_polling(self):
|
||||
_thread.start_new_thread(self._event_poller, ())
|
||||
# start a thread and pass in the mainloop
|
||||
_thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),))
|
||||
|
||||
|
||||
def is_module_running(self, module):
|
||||
s = self.session()
|
||||
@ -465,4 +481,4 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
plugins.on('channel_hop', self, channel)
|
||||
|
||||
except Exception as e:
|
||||
logging.error("error: %s", e)
|
||||
logging.error("Error while setting channel (%s)", e)
|
||||
|
@ -1,12 +1,9 @@
|
||||
import os
|
||||
import time
|
||||
import warnings
|
||||
import logging
|
||||
|
||||
# https://stackoverflow.com/questions/40426502/is-there-a-way-to-suppress-the-messages-tensorflow-prints/40426709
|
||||
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # or any {'0', '1', '2'}
|
||||
# https://stackoverflow.com/questions/15777951/how-to-suppress-pandas-future-warning
|
||||
warnings.simplefilter(action='ignore', category=FutureWarning)
|
||||
|
||||
|
||||
def load(config, agent, epoch, from_disk=True):
|
||||
@ -48,8 +45,17 @@ def load(config, agent, epoch, from_disk=True):
|
||||
if from_disk and os.path.exists(config['path']):
|
||||
logging.info("[ai] loading %s ..." % config['path'])
|
||||
start = time.time()
|
||||
a2c.load(config['path'], env)
|
||||
logging.debug("[ai] A2C loaded in %.2fs" % (time.time() - start))
|
||||
try:
|
||||
a2c.load(config['path'], env)
|
||||
except AssertionError as as_err:
|
||||
from fnmatch import fnmatch
|
||||
# Sometimes the model breaks...
|
||||
if not fnmatch(str(as_err), '* same * space as the model *'):
|
||||
raise as_err
|
||||
else:
|
||||
logging.warning("[ai] Model could not be loaded. Using new model.")
|
||||
else:
|
||||
logging.debug("[ai] A2C loaded in %.2fs" % (time.time() - start))
|
||||
else:
|
||||
logging.info("[ai] model created:")
|
||||
for key, value in config['params'].items():
|
||||
@ -59,7 +65,7 @@ def load(config, agent, epoch, from_disk=True):
|
||||
|
||||
return a2c
|
||||
except Exception as e:
|
||||
logging.exception("error while starting AI")
|
||||
logging.exception("error while starting AI (%s)", e)
|
||||
|
||||
logging.warning("[ai] AI not loaded!")
|
||||
return False
|
||||
|
@ -19,6 +19,10 @@ class Epoch(object):
|
||||
self.active_for = 0
|
||||
# number of epochs with no visible access points
|
||||
self.blind_for = 0
|
||||
# number of epochs in sad state
|
||||
self.sad_for = 0
|
||||
# number of epochs in bored state
|
||||
self.bored_for = 0
|
||||
# did deauth in this epoch in the current channel?
|
||||
self.did_deauth = False
|
||||
# number of deauths in this epoch
|
||||
@ -99,13 +103,13 @@ class Epoch(object):
|
||||
try:
|
||||
aps_per_chan[ch_idx] += 1.0
|
||||
sta_per_chan[ch_idx] += len(ap['clients'])
|
||||
except IndexError as e:
|
||||
except IndexError:
|
||||
logging.error("got data on channel %d, we can store %d channels" % (ap['channel'], wifi.NumChannels))
|
||||
|
||||
for peer in peers:
|
||||
try:
|
||||
peers_per_chan[peer.last_channel - 1] += 1.0
|
||||
except IndexError as e:
|
||||
except IndexError:
|
||||
logging.error(
|
||||
"got peer data on channel %d, we can store %d channels" % (peer.last_channel, wifi.NumChannels))
|
||||
|
||||
@ -157,6 +161,20 @@ class Epoch(object):
|
||||
else:
|
||||
self.active_for += 1
|
||||
self.inactive_for = 0
|
||||
self.sad_for = 0
|
||||
self.bored_for = 0
|
||||
|
||||
if self.inactive_for >= self.config['personality']['sad_num_epochs']:
|
||||
# sad > bored; cant be sad and bored
|
||||
self.bored_for = 0
|
||||
self.sad_for += 1
|
||||
elif self.inactive_for >= self.config['personality']['bored_num_epochs']:
|
||||
# sad_treshhold > inactive > bored_treshhold; cant be sad and bored
|
||||
self.sad_for = 0
|
||||
self.bored_for += 1
|
||||
else:
|
||||
self.sad_for = 0
|
||||
self.bored_for = 0
|
||||
|
||||
now = time.time()
|
||||
cpu = pwnagotchi.cpu_load()
|
||||
@ -172,6 +190,8 @@ class Epoch(object):
|
||||
'blind_for_epochs': self.blind_for,
|
||||
'inactive_for_epochs': self.inactive_for,
|
||||
'active_for_epochs': self.active_for,
|
||||
'sad_for_epochs': self.sad_for,
|
||||
'bored_for_epochs': self.bored_for,
|
||||
'missed_interactions': self.num_missed,
|
||||
'num_hops': self.num_hops,
|
||||
'num_peers': self.num_peers,
|
||||
@ -188,13 +208,15 @@ class Epoch(object):
|
||||
self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data)
|
||||
self._epoch_data_ready.set()
|
||||
|
||||
logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d peers=%d tot_bond=%.2f "
|
||||
logging.info("[epoch %d] duration=%s slept_for=%s blind=%d sad=%d bored=%d inactive=%d active=%d peers=%d tot_bond=%.2f "
|
||||
"avg_bond=%.2f hops=%d missed=%d deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% "
|
||||
"temperature=%dC reward=%s" % (
|
||||
self.epoch,
|
||||
utils.secs_to_hhmmss(self.epoch_duration),
|
||||
utils.secs_to_hhmmss(self.num_slept),
|
||||
self.blind_for,
|
||||
self.sad_for,
|
||||
self.bored_for,
|
||||
self.inactive_for,
|
||||
self.active_for,
|
||||
self.num_peers,
|
||||
|
@ -18,4 +18,10 @@ class RewardFunction(object):
|
||||
m = -.3 * (state['missed_interactions'] / tot_interactions)
|
||||
i = -.2 * (state['inactive_for_epochs'] / tot_epochs)
|
||||
|
||||
return h + a + c + b + i + m
|
||||
# include emotions if state >= 5 epochs
|
||||
_sad = state['sad_for_epochs'] if state['sad_for_epochs'] >= 5 else 0
|
||||
_bored = state['bored_for_epochs'] if state['bored_for_epochs'] >= 5 else 0
|
||||
s = -.2 * (_sad / tot_epochs)
|
||||
l = -.1 * (_bored / tot_epochs)
|
||||
|
||||
return h + a + c + b + i + m + s + l
|
||||
|
@ -176,7 +176,7 @@ class AsyncTrainer(object):
|
||||
self.set_training(True, epochs_per_episode)
|
||||
self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step)
|
||||
except Exception as e:
|
||||
logging.exception("[ai] error while training")
|
||||
logging.exception("[ai] error while training (%s)", e)
|
||||
finally:
|
||||
self.set_training(False)
|
||||
obs = self._model.env.reset()
|
||||
|
@ -120,14 +120,14 @@ class Automata(object):
|
||||
logging.warning("agent missed %d interactions -> lonely", did_miss)
|
||||
self.set_lonely()
|
||||
# after X times being bored, the status is set to sad or angry
|
||||
elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
|
||||
elif self._epoch.sad_for:
|
||||
factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs']
|
||||
if factor >= 2.0:
|
||||
self.set_angry(factor)
|
||||
else:
|
||||
self.set_sad()
|
||||
# after X times being inactive, the status is set to bored
|
||||
elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
|
||||
elif self._epoch.bored_for:
|
||||
self.set_bored()
|
||||
# after X times being active, the status is set to happy / excited
|
||||
elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']:
|
||||
|
@ -1,7 +1,21 @@
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
import websockets
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from time import sleep
|
||||
|
||||
requests.adapters.DEFAULT_RETRIES = 5 # increase retries number
|
||||
|
||||
ping_timeout = 180
|
||||
ping_interval = 15
|
||||
max_queue = 10000
|
||||
|
||||
min_sleep = 0.5
|
||||
max_sleep = 5.0
|
||||
|
||||
def decode(r, verbose_errors=True):
|
||||
try:
|
||||
@ -25,16 +39,81 @@ class Client(object):
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.url = "%s://%s:%d/api" % (scheme, hostname, port)
|
||||
self.websocket = "ws://%s:%s@%s:%d/api" % (username, password, hostname, port)
|
||||
self.auth = HTTPBasicAuth(username, password)
|
||||
|
||||
def session(self):
|
||||
r = requests.get("%s/session" % self.url, auth=self.auth)
|
||||
# session takes optional argument to pull a sub-dictionary
|
||||
# ex.: "session/wifi", "session/ble"
|
||||
def session(self, sess="session"):
|
||||
r = requests.get("%s/%s" % (self.url, sess), auth=self.auth)
|
||||
return decode(r)
|
||||
|
||||
def events(self):
|
||||
r = requests.get("%s/events" % self.url, auth=self.auth)
|
||||
return decode(r)
|
||||
async def start_websocket(self, consumer):
|
||||
s = "%s/events" % self.websocket
|
||||
|
||||
# More modern version of the approach below
|
||||
# logging.info("Creating new websocket...")
|
||||
# async for ws in websockets.connect(s):
|
||||
# try:
|
||||
# async for msg in ws:
|
||||
# try:
|
||||
# await consumer(msg)
|
||||
# except Exception as ex:
|
||||
# logging.debug("Error while parsing event (%s)", ex)
|
||||
# except websockets.exceptions.ConnectionClosedError:
|
||||
# sleep_time = max_sleep*random.random()
|
||||
# logging.warning('Retrying websocket connection in {} sec'.format(sleep_time))
|
||||
# await asyncio.sleep(sleep_time)
|
||||
# continue
|
||||
|
||||
# restarted every time the connection fails
|
||||
while True:
|
||||
logging.info("creating new websocket...")
|
||||
try:
|
||||
async with websockets.connect(s, ping_interval=ping_interval, ping_timeout=ping_timeout, max_queue=max_queue) as ws:
|
||||
# listener loop
|
||||
while True:
|
||||
try:
|
||||
async for msg in ws:
|
||||
try:
|
||||
await consumer(msg)
|
||||
except Exception as ex:
|
||||
logging.debug("error while parsing event (%s)", ex)
|
||||
except websockets.exceptions.ConnectionClosedError:
|
||||
try:
|
||||
pong = await ws.ping()
|
||||
await asyncio.wait_for(pong, timeout=ping_timeout)
|
||||
logging.warning('ping OK, keeping connection alive...')
|
||||
continue
|
||||
except:
|
||||
sleep_time = min_sleep + max_sleep*random.random()
|
||||
logging.warning('ping error - retrying connection in {} sec'.format(sleep_time))
|
||||
await asyncio.sleep(sleep_time)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
sleep_time = min_sleep + max_sleep*random.random()
|
||||
logging.warning('nobody seems to be listening at the bettercap endpoint...')
|
||||
logging.warning('retrying connection in {} sec'.format(sleep_time))
|
||||
await asyncio.sleep(sleep_time)
|
||||
continue
|
||||
except OSError:
|
||||
sleep_time = min_sleep + max_sleep*random.random()
|
||||
logging.warning('connection to the bettercap endpoint failed...')
|
||||
logging.warning('retrying connection in {} sec'.format(sleep_time))
|
||||
await asyncio.sleep(sleep_time)
|
||||
continue
|
||||
|
||||
|
||||
def run(self, command, verbose_errors=True):
|
||||
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})
|
||||
while True:
|
||||
try:
|
||||
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
sleep_time = min_sleep + max_sleep*random.random()
|
||||
logging.warning("can't run my request... connection to the bettercap endpoint failed...")
|
||||
logging.warning('retrying run in {} sec'.format(sleep_time))
|
||||
sleep(sleep_time)
|
||||
else:
|
||||
break
|
||||
|
||||
return decode(r, verbose_errors=verbose_errors)
|
||||
|
244
pwnagotchi/defaults.toml
Normal file
244
pwnagotchi/defaults.toml
Normal file
@ -0,0 +1,244 @@
|
||||
main.name = ""
|
||||
main.lang = "en"
|
||||
main.confd = "/etc/pwnagotchi/conf.d/"
|
||||
main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins"
|
||||
main.custom_plugin_repos = [
|
||||
"https://git.chadwaltercummings.me/scifijunkie/pwnagotchi-plugins-contrib.git"
|
||||
]
|
||||
main.iface = "mon0"
|
||||
main.mon_start_cmd = "/usr/bin/monstart"
|
||||
main.mon_stop_cmd = "/usr/bin/monstop"
|
||||
main.mon_max_blind_epochs = 50
|
||||
main.no_restart = false
|
||||
main.whitelist = [
|
||||
"EXAMPLE_NETWORK",
|
||||
"ANOTHER_EXAMPLE_NETWORK",
|
||||
"fo:od:ba:be:fo:od",
|
||||
"fo:od:ba"
|
||||
]
|
||||
main.filter = ""
|
||||
|
||||
main.log.debug = false
|
||||
|
||||
main.plugins.grid.enabled = true
|
||||
main.plugins.grid.report = false
|
||||
main.plugins.grid.exclude = [
|
||||
"YourHomeNetworkHere"
|
||||
]
|
||||
|
||||
main.plugins.auto-update.enabled = true
|
||||
main.plugins.auto-update.install = true
|
||||
main.plugins.auto-update.interval = 1
|
||||
|
||||
main.plugins.net-pos.enabled = false
|
||||
main.plugins.net-pos.api_key = "test"
|
||||
|
||||
main.plugins.gps.enabled = false
|
||||
main.plugins.gps.speed = 19200
|
||||
main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947"
|
||||
|
||||
main.plugins.webgpsmap.enabled = false
|
||||
|
||||
main.plugins.onlinehashcrack.enabled = false
|
||||
main.plugins.onlinehashcrack.email = ""
|
||||
main.plugins.onlinehashcrack.dashboard = ""
|
||||
main.plugins.onlinehashcrack.single_files = false
|
||||
main.plugins.onlinehashcrack.whitelist = []
|
||||
|
||||
main.plugins.wpa-sec.enabled = false
|
||||
main.plugins.wpa-sec.api_key = ""
|
||||
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
|
||||
main.plugins.wpa-sec.download_results = false
|
||||
main.plugins.wpa-sec.download_interval = 3600
|
||||
main.plugins.wpa-sec.whitelist = []
|
||||
|
||||
main.plugins.wigle.enabled = false
|
||||
main.plugins.wigle.api_key = ""
|
||||
main.plugins.wigle.whitelist = []
|
||||
main.plugins.wigle.donate = true
|
||||
|
||||
main.plugins.bt-tether.enabled = false
|
||||
|
||||
main.plugins.bt-tether.devices.android-phone.enabled = false
|
||||
main.plugins.bt-tether.devices.android-phone.search_order = 1
|
||||
main.plugins.bt-tether.devices.android-phone.mac = ""
|
||||
main.plugins.bt-tether.devices.android-phone.ip = "192.168.44.44"
|
||||
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.scantime = 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.priority = 1
|
||||
|
||||
main.plugins.bt-tether.devices.ios-phone.enabled = false
|
||||
main.plugins.bt-tether.devices.ios-phone.search_order = 2
|
||||
main.plugins.bt-tether.devices.ios-phone.mac = ""
|
||||
main.plugins.bt-tether.devices.ios-phone.ip = "172.20.10.6"
|
||||
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.scantime = 20
|
||||
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.priority = 999
|
||||
|
||||
main.plugins.memtemp.enabled = false
|
||||
main.plugins.memtemp.scale = "celsius"
|
||||
main.plugins.memtemp.orientation = "horizontal"
|
||||
|
||||
main.plugins.paw-gps.enabled = false
|
||||
main.plugins.paw-gps.ip = "192.168.44.1:8080"
|
||||
|
||||
main.plugins.ups_lite.enabled = false
|
||||
main.plugins.ups_lite.shutdown = 2
|
||||
|
||||
main.plugins.gpio_buttons.enabled = false
|
||||
|
||||
main.plugins.led.enabled = true
|
||||
main.plugins.led.led = 0
|
||||
main.plugins.led.delay = 200
|
||||
main.plugins.led.patterns.loaded = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.updating = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.unread_inbox = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.ready = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.ai_ready = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.ai_training_start = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.ai_best_reward = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.ai_worst_reward = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.bored = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.sad = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.excited = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.lonely = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.rebooting = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.wait = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.sleep = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.wifi_update = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.association = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.deauthentication = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.handshake = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.epoch = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.peer_detected = "oo oo oo oo oo oo oo"
|
||||
main.plugins.led.patterns.peer_lost = "oo oo oo oo oo oo oo"
|
||||
|
||||
main.plugins.logtail.enabled = false
|
||||
main.plugins.logtail.max-lines = 10000
|
||||
|
||||
main.plugins.session-stats.enabled = true
|
||||
main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/"
|
||||
|
||||
main.log.path = "/var/log/pwnagotchi.log"
|
||||
main.log.rotation.enabled = true
|
||||
main.log.rotation.size = "10M"
|
||||
|
||||
ai.enabled = true
|
||||
ai.path = "/root/brain.nn"
|
||||
ai.laziness = 0.1
|
||||
ai.epochs_per_episode = 50
|
||||
|
||||
ai.params.gamma = 0.99
|
||||
ai.params.n_steps = 1
|
||||
ai.params.vf_coef = 0.25
|
||||
ai.params.ent_coef = 0.01
|
||||
ai.params.max_grad_norm = 0.5
|
||||
ai.params.learning_rate = 0.001
|
||||
ai.params.alpha = 0.99
|
||||
ai.params.epsilon = 0.00001
|
||||
ai.params.verbose = 1
|
||||
ai.params.lr_schedule = "constant"
|
||||
|
||||
personality.advertise = true
|
||||
personality.deauth = true
|
||||
personality.associate = true
|
||||
personality.channels = []
|
||||
personality.min_rssi = -200
|
||||
personality.ap_ttl = 120
|
||||
personality.sta_ttl = 300
|
||||
personality.recon_time = 30
|
||||
personality.max_inactive_scale = 2
|
||||
personality.recon_inactive_multiplier = 2
|
||||
personality.hop_recon_time = 10
|
||||
personality.min_recon_time = 5
|
||||
personality.max_interactions = 3
|
||||
personality.max_misses_for_recon = 5
|
||||
personality.excited_num_epochs = 10
|
||||
personality.bored_num_epochs = 15
|
||||
personality.sad_num_epochs = 25
|
||||
personality.bond_encounters_factor = 20000
|
||||
|
||||
ui.fps = 0.0
|
||||
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
|
||||
ui.font.size_offset = 0 # will be added to the font size
|
||||
|
||||
ui.faces.look_r = "( ⚆_⚆)"
|
||||
ui.faces.look_l = "(☉_☉ )"
|
||||
ui.faces.look_r_happy = "( ◕‿◕)"
|
||||
ui.faces.look_l_happy = "(◕‿◕ )"
|
||||
ui.faces.sleep = "(⇀‿‿↼)"
|
||||
ui.faces.sleep2 = "(≖‿‿≖)"
|
||||
ui.faces.awake = "(◕‿‿◕)"
|
||||
ui.faces.bored = "(-__-)"
|
||||
ui.faces.intense = "(°▃▃°)"
|
||||
ui.faces.cool = "(⌐■_■)"
|
||||
ui.faces.happy = "(•‿‿•)"
|
||||
ui.faces.excited = "(ᵔ◡◡ᵔ)"
|
||||
ui.faces.grateful = "(^‿‿^)"
|
||||
ui.faces.motivated = "(☼‿‿☼)"
|
||||
ui.faces.demotivated = "(≖__≖)"
|
||||
ui.faces.smart = "(✜‿‿✜)"
|
||||
ui.faces.lonely = "(ب__ب)"
|
||||
ui.faces.sad = "(╥☁╥ )"
|
||||
ui.faces.angry = "(-_-')"
|
||||
ui.faces.friend = "(♥‿‿♥)"
|
||||
ui.faces.broken = "(☓‿‿☓)"
|
||||
ui.faces.debug = "(#__#)"
|
||||
ui.faces.upload = "(1__0)"
|
||||
ui.faces.upload1 = "(1__1)"
|
||||
ui.faces.upload2 = "(0__1)"
|
||||
|
||||
ui.web.enabled = true
|
||||
ui.web.address = "0.0.0.0"
|
||||
ui.web.username = "changeme"
|
||||
ui.web.password = "changeme"
|
||||
ui.web.origin = ""
|
||||
ui.web.port = 8080
|
||||
ui.web.on_frame = ""
|
||||
|
||||
ui.display.enabled = true
|
||||
ui.display.rotation = 180
|
||||
ui.display.type = "waveshare_2"
|
||||
ui.display.color = "black"
|
||||
|
||||
bettercap.scheme = "http"
|
||||
bettercap.hostname = "localhost"
|
||||
bettercap.port = 8081
|
||||
bettercap.username = "pwnagotchi"
|
||||
bettercap.password = "pwnagotchi"
|
||||
bettercap.handshakes = "/root/handshakes"
|
||||
bettercap.silence = [
|
||||
"ble.device.new",
|
||||
"ble.device.lost",
|
||||
"ble.device.disconnected",
|
||||
"ble.device.connected",
|
||||
"ble.device.service.discovered",
|
||||
"ble.device.characteristic.discovered",
|
||||
"wifi.client.new",
|
||||
"wifi.client.lost",
|
||||
"wifi.client.probe",
|
||||
"wifi.ap.new",
|
||||
"wifi.ap.lost",
|
||||
"mod.started"
|
||||
]
|
||||
|
||||
fs.memory.enabled = false
|
||||
fs.memory.mounts.log.enabled = false
|
||||
fs.memory.mounts.log.mount = "/var/log"
|
||||
fs.memory.mounts.log.size = "50M"
|
||||
fs.memory.mounts.log.sync = 60
|
||||
fs.memory.mounts.log.zram = true
|
||||
fs.memory.mounts.log.rsync = true
|
||||
|
||||
fs.memory.mounts.data.enabled = false
|
||||
fs.memory.mounts.data.mount = "/var/tmp/pwnagotchi"
|
||||
fs.memory.mounts.data.size = "10M"
|
||||
fs.memory.mounts.data.sync = 3600
|
||||
fs.memory.mounts.data.zram = false
|
||||
fs.memory.mounts.data.rsync = true
|
@ -1,295 +0,0 @@
|
||||
#
|
||||
# This file is recreated with default settings on every pwnagotchi restart,
|
||||
# use /etc/pwnagotchi/config.yml to configure this unit.
|
||||
#
|
||||
#
|
||||
# main algorithm configuration
|
||||
main:
|
||||
# if set this will set the hostname of the unit. min length is 2, max length 25, only a-zA-Z0-9- allowed
|
||||
name: ''
|
||||
# currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es, pt
|
||||
lang: en
|
||||
# custom plugins path, if null only default plugins with be loaded
|
||||
custom_plugins:
|
||||
# which plugins to load and enable
|
||||
plugins:
|
||||
grid:
|
||||
enabled: true
|
||||
report: false # don't report pwned networks by default!
|
||||
exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs)
|
||||
- YourHomeNetworkHere
|
||||
auto-update:
|
||||
enabled: true
|
||||
install: true # if false, it will only warn that updates are available, if true it will install them
|
||||
interval: 1 # every 1 hour
|
||||
net-pos:
|
||||
enabled: false
|
||||
api_key: 'test'
|
||||
gps:
|
||||
enabled: false
|
||||
speed: 19200
|
||||
device: /dev/ttyUSB0
|
||||
webgpsmap:
|
||||
enabled: false
|
||||
onlinehashcrack:
|
||||
enabled: false
|
||||
email: ~
|
||||
wpa-sec:
|
||||
enabled: false
|
||||
api_key: ~
|
||||
api_url: "https://wpa-sec.stanev.org"
|
||||
download_results: false
|
||||
wigle:
|
||||
enabled: false
|
||||
api_key: ~
|
||||
bt-tether:
|
||||
enabled: false # if you want to use this, set ui.display.web.address to 0.0.0.0
|
||||
devices:
|
||||
android-phone:
|
||||
enabled: false
|
||||
search_order: 1 # search for this first
|
||||
mac: ~ # mac of your bluetooth device
|
||||
ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
|
||||
netmask: 24
|
||||
interval: 1 # check every minute for device
|
||||
scantime: 10 # search for 10 seconds
|
||||
max_tries: 10 # after 10 tries of "not found"; don't try anymore
|
||||
share_internet: false
|
||||
priority: 1 # low routing priority; ios (prio: 999) would win here
|
||||
ios-phone:
|
||||
enabled: false
|
||||
search_order: 2 # search for this second
|
||||
mac: ~ # mac of your bluetooth device
|
||||
ip: '172.20.10.6' # ip from which your pwnagotchi should be reachable
|
||||
netmask: 24
|
||||
interval: 5 # check every 5 minutes for device
|
||||
scantime: 20
|
||||
max_tries: 0 # infinity
|
||||
share_internet: false
|
||||
priority: 999 # routing priority
|
||||
memtemp: # Display memory usage, cpu load and cpu temperature on screen
|
||||
enabled: false
|
||||
scale: celsius
|
||||
orientation: horizontal # horizontal/vertical
|
||||
paw-gps:
|
||||
enabled: false
|
||||
#The IP Address of your phone with Paw Server running, default (option is empty) is 192.168.44.1
|
||||
ip: ''
|
||||
gpio_buttons:
|
||||
enabled: false
|
||||
#The following is a list of the GPIO number for your button, and the command you want to run when it is pressed
|
||||
gpios:
|
||||
#20: 'touch /root/.pwnagotchi-auto && systemctl restart pwnagotchi'
|
||||
#21: 'shutdown -h now'
|
||||
led:
|
||||
enabled: true
|
||||
# for /sys/class/leds/led0/brightness
|
||||
led: 0
|
||||
# time in milliseconds for each element of the patterns
|
||||
delay: 200
|
||||
# o=on space=off, comment the ones you don't want
|
||||
patterns:
|
||||
loaded: 'oo oo oo oo oo oo oo'
|
||||
updating: 'oo oo oo oo oo oo oo'
|
||||
# internet_available: 'oo oo oo oo oo oo oo'
|
||||
unread_inbox: 'oo oo oo oo oo oo oo'
|
||||
ready: 'oo oo oo oo oo oo oo'
|
||||
ai_ready: 'oo oo oo oo oo oo oo'
|
||||
ai_training_start: 'oo oo oo oo oo oo oo'
|
||||
ai_best_reward: 'oo oo oo oo oo oo oo'
|
||||
ai_worst_reward: 'oo oo oo oo oo oo oo'
|
||||
bored: 'oo oo oo oo oo oo oo'
|
||||
sad: 'oo oo oo oo oo oo oo'
|
||||
excited: 'oo oo oo oo oo oo oo'
|
||||
lonely: 'oo oo oo oo oo oo oo'
|
||||
rebooting: 'oo oo oo oo oo oo oo'
|
||||
wait: 'oo oo oo oo oo oo oo'
|
||||
sleep: 'oo oo oo oo oo oo oo'
|
||||
wifi_update: 'oo oo oo oo oo oo oo'
|
||||
association: 'oo oo oo oo oo oo oo'
|
||||
deauthentication: 'oo oo oo oo oo oo oo'
|
||||
handshake: 'oo oo oo oo oo oo oo'
|
||||
epoch: 'oo oo oo oo oo oo oo'
|
||||
peer_detected: 'oo oo oo oo oo oo oo'
|
||||
peer_lost: 'oo oo oo oo oo oo oo'
|
||||
webcfg:
|
||||
enabled: false
|
||||
session-stats:
|
||||
enabled: true
|
||||
# monitor interface to use
|
||||
iface: mon0
|
||||
# command to run to bring the mon interface up in case it's not up already
|
||||
mon_start_cmd: /usr/bin/monstart
|
||||
mon_stop_cmd: /usr/bin/monstop
|
||||
mon_max_blind_epochs: 50
|
||||
# if true, will not restart the wifi module
|
||||
no_restart: false
|
||||
# access points to ignore. Could be the ssid, bssid or the vendor part of bssid.
|
||||
whitelist:
|
||||
- EXAMPLE_NETWORK
|
||||
- ANOTHER_EXAMPLE_NETWORK
|
||||
- fo:od:ba:be:fo:od # BSSID
|
||||
- fo:od:ba # Vendor BSSID
|
||||
# if not null, filter access points by this regular expression
|
||||
filter: null
|
||||
# logging
|
||||
log:
|
||||
# file to log to
|
||||
path: /var/log/pwnagotchi.log
|
||||
rotation:
|
||||
enabled: true
|
||||
# specify a maximum size to rotate ( format is 10/10B, 10K, 10M 10G )
|
||||
size: '10M'
|
||||
|
||||
ai:
|
||||
# if false, only the default 'personality' will be used
|
||||
enabled: true
|
||||
path: /root/brain.nn
|
||||
# 1.0 - laziness = probability of start training
|
||||
laziness: 0.1
|
||||
# how many epochs to train on
|
||||
epochs_per_episode: 50
|
||||
params:
|
||||
# discount factor
|
||||
gamma: 0.99
|
||||
# the number of steps to run for each environment per update
|
||||
n_steps: 1
|
||||
# value function coefficient for the loss calculation
|
||||
vf_coef: 0.25
|
||||
# entropy coefficient for the loss calculation
|
||||
ent_coef: 0.01
|
||||
# maximum value for the gradient clipping
|
||||
max_grad_norm: 0.5
|
||||
# the learning rate
|
||||
learning_rate: 0.0010
|
||||
# rmsprop decay parameter
|
||||
alpha: 0.99
|
||||
# rmsprop epsilon
|
||||
epsilon: 0.00001
|
||||
# the verbosity level: 0 none, 1 training information, 2 tensorflow debug
|
||||
verbose: 1
|
||||
# type of scheduler for the learning rate update ('linear', 'constant', 'double_linear_con', 'middle_drop' or 'double_middle_drop')
|
||||
lr_schedule: 'constant'
|
||||
# the log location for tensorboard (if None, no logging)
|
||||
tensorboard_log: null
|
||||
|
||||
personality:
|
||||
# advertise our presence
|
||||
advertise: true
|
||||
# perform a deauthentication attack to client stations in order to get full or half handshakes
|
||||
deauth: true
|
||||
# send association frames to APs in order to get the PMKID
|
||||
associate: true
|
||||
# list of channels to recon on, or empty for all channels
|
||||
channels: []
|
||||
# minimum WiFi signal strength in dBm
|
||||
min_rssi: -200
|
||||
# number of seconds for wifi.ap.ttl
|
||||
ap_ttl: 120
|
||||
# number of seconds for wifi.sta.ttl
|
||||
sta_ttl: 300
|
||||
# time in seconds to wait during channel recon
|
||||
recon_time: 30
|
||||
# number of inactive epochs after which recon_time gets multiplied by recon_inactive_multiplier
|
||||
max_inactive_scale: 2
|
||||
# if more than max_inactive_scale epochs are inactive, recon_time *= recon_inactive_multiplier
|
||||
recon_inactive_multiplier: 2
|
||||
# time in seconds to wait during channel hopping if activity has been performed
|
||||
hop_recon_time: 10
|
||||
# time in seconds to wait during channel hopping if no activity has been performed
|
||||
min_recon_time: 5
|
||||
# maximum amount of deauths/associations per BSSID per session
|
||||
max_interactions: 3
|
||||
# maximum amount of misses before considering the data stale and triggering a new recon
|
||||
max_misses_for_recon: 5
|
||||
# number of active epochs that triggers the excited state
|
||||
excited_num_epochs: 10
|
||||
# number of inactive epochs that triggers the bored state
|
||||
bored_num_epochs: 15
|
||||
# number of inactive epochs that triggers the sad state
|
||||
sad_num_epochs: 25
|
||||
# number of encounters (times met on a channel) with another unit before considering it a friend and bond
|
||||
# also used for cumulative bonding score of nearby units
|
||||
bond_encounters_factor: 20000
|
||||
|
||||
# ui configuration
|
||||
ui:
|
||||
# here you can customize the faces
|
||||
faces:
|
||||
look_r: '( ⚆_⚆)'
|
||||
look_l: '(☉_☉ )'
|
||||
look_r_happy: '( ◕‿◕)'
|
||||
look_l_happy: '(◕‿◕ )'
|
||||
sleep: '(⇀‿‿↼)'
|
||||
sleep2: '(≖‿‿≖)'
|
||||
awake: '(◕‿‿◕)'
|
||||
bored: '(-__-)'
|
||||
intense: '(°▃▃°)'
|
||||
cool: '(⌐■_■)'
|
||||
happy: '(•‿‿•)'
|
||||
excited: '(ᵔ◡◡ᵔ)'
|
||||
grateful: '(^‿‿^)'
|
||||
motivated: '(☼‿‿☼)'
|
||||
demotivated: '(≖__≖)'
|
||||
smart: '(✜‿‿✜)'
|
||||
lonely: '(ب__ب)'
|
||||
sad: '(╥☁╥ )'
|
||||
angry: "(-_-')"
|
||||
friend: '(♥‿‿♥)'
|
||||
broken: '(☓‿‿☓)'
|
||||
debug: '(#__#)'
|
||||
# ePaper display can update every 3 secs anyway, set to 0 to only refresh for major data changes
|
||||
# IMPORTANT: The lifespan of an eINK display depends on the cumulative amount of refreshes. If you want to
|
||||
# preserve your display over time, you should set this value to 0.0 so that the display will be refreshed only
|
||||
# if any of the important data fields changed (the uptime and blinking cursor won't trigger a refresh).
|
||||
fps: 0.0
|
||||
# web ui
|
||||
web:
|
||||
enabled: true
|
||||
address: '0.0.0.0'
|
||||
username: changeme # !!! CHANGE THIS !!!
|
||||
password: changeme # !!! CHANGE THIS !!!
|
||||
origin: null
|
||||
port: 8080
|
||||
# command to be executed when a new png frame is available
|
||||
# for instance, to use with framebuffer based displays:
|
||||
# on_frame: 'fbi --noverbose -a -d /dev/fb1 -T 1 /root/pwnagotchi.png > /dev/null 2>&1'
|
||||
on_frame: ''
|
||||
# hardware display
|
||||
display:
|
||||
enabled: true
|
||||
rotation: 180
|
||||
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare154inch, waveshare27inch, waveshare29inch, dfrobot/df, waveshare144lcd/ws144inch
|
||||
type: 'waveshare_2'
|
||||
# Possible options red/yellow/black (black used for monocromatic displays)
|
||||
# Waveshare tri-color 2.13in display can be over-driven with color set as 'fastAndFurious'
|
||||
# THIS IS POTENTIALLY DANGEROUS. DO NOT USE UNLESS YOU UNDERSTAND THAT IT COULD KILL YOUR DISPLAY
|
||||
color: 'black'
|
||||
|
||||
# bettercap rest api configuration
|
||||
bettercap:
|
||||
# api scheme://hostname:port username and password
|
||||
scheme: http
|
||||
hostname: localhost
|
||||
port: 8081
|
||||
username: pwnagotchi
|
||||
password: pwnagotchi
|
||||
# folder where bettercap stores the WPA handshakes, given that
|
||||
# wifi.handshakes.aggregate will be set to false and individual
|
||||
# pcap files will be created in order to minimize the chances
|
||||
# of a single pcap file to get corrupted
|
||||
handshakes: /root/handshakes
|
||||
# events to mute in bettercap's events stream
|
||||
silence:
|
||||
- ble.device.new
|
||||
- ble.device.lost
|
||||
- ble.device.disconnected
|
||||
- ble.device.connected
|
||||
- ble.device.service.discovered
|
||||
- ble.device.characteristic.discovered
|
||||
- wifi.client.new
|
||||
- wifi.client.lost
|
||||
- wifi.client.probe
|
||||
- wifi.ap.new
|
||||
- wifi.ap.lost
|
||||
- mod.started
|
190
pwnagotchi/fs/__init__.py
Normal file
190
pwnagotchi/fs/__init__.py
Normal file
@ -0,0 +1,190 @@
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import contextlib
|
||||
import shutil
|
||||
import _thread
|
||||
import logging
|
||||
|
||||
from time import sleep
|
||||
from distutils.dir_util import copy_tree
|
||||
|
||||
mounts = list()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ensure_write(filename, mode='w'):
|
||||
path = os.path.dirname(filename)
|
||||
fd, tmp = tempfile.mkstemp(dir=path)
|
||||
|
||||
with os.fdopen(fd, mode) as f:
|
||||
yield f
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
|
||||
os.replace(tmp, filename)
|
||||
|
||||
|
||||
def size_of(path):
|
||||
"""
|
||||
Calculate the sum of all the files in path
|
||||
"""
|
||||
total = 0
|
||||
for root, _, files in os.walk(path):
|
||||
for f in files:
|
||||
total += os.path.getsize(os.path.join(root, f))
|
||||
return total
|
||||
|
||||
|
||||
def is_mountpoint(path):
|
||||
"""
|
||||
Checks if path is mountpoint
|
||||
"""
|
||||
return os.system(f"mountpoint -q {path}") == 0
|
||||
|
||||
|
||||
def setup_mounts(config):
|
||||
"""
|
||||
Sets up all the configured mountpoints
|
||||
"""
|
||||
global mounts
|
||||
fs_cfg = config['fs']['memory']
|
||||
if not fs_cfg['enabled']:
|
||||
return
|
||||
|
||||
for name, options in fs_cfg['mounts'].items():
|
||||
if not options['enabled']:
|
||||
continue
|
||||
logging.debug("[FS] Trying to setup mount %s (%s)", name, options['mount'])
|
||||
size,unit = re.match(r"(\d+)([a-zA-Z]+)", options['size']).groups()
|
||||
target = os.path.join('/run/pwnagotchi/disk/', os.path.basename(options['mount']))
|
||||
|
||||
is_mounted = is_mountpoint(target)
|
||||
logging.debug("[FS] %s is %s mounted", options['mount'],
|
||||
"already" if is_mounted else "not yet")
|
||||
|
||||
m = MemoryFS(
|
||||
options['mount'],
|
||||
target,
|
||||
size=options['size'],
|
||||
zram=options['zram'],
|
||||
zram_disk_size=f"{int(size)*2}{unit}",
|
||||
rsync=options['rsync'])
|
||||
|
||||
if not is_mounted:
|
||||
if not m.mount():
|
||||
logging.debug(f"Error while mounting {m.mountpoint}")
|
||||
continue
|
||||
|
||||
if not m.sync(to_ram=True):
|
||||
logging.debug(f"Error while syncing to {m.mountpoint}")
|
||||
m.umount()
|
||||
continue
|
||||
|
||||
interval = int(options['sync'])
|
||||
if interval:
|
||||
logging.debug("[FS] Starting thread to sync %s (interval: %d)",
|
||||
options['mount'], interval)
|
||||
_thread.start_new_thread(m.daemonize, (interval,))
|
||||
else:
|
||||
logging.debug("[FS] Not syncing %s, because interval is 0",
|
||||
options['mount'])
|
||||
|
||||
mounts.append(m)
|
||||
|
||||
|
||||
class MemoryFS:
|
||||
@staticmethod
|
||||
def zram_install():
|
||||
if not os.path.exists("/sys/class/zram-control"):
|
||||
logging.debug("[FS] Installing zram")
|
||||
return os.system("modprobe zram") == 0
|
||||
return True
|
||||
|
||||
|
||||
@staticmethod
|
||||
def zram_dev():
|
||||
logging.debug("[FS] Adding zram device")
|
||||
return open("/sys/class/zram-control/hot_add", "rt").read().strip("\n")
|
||||
|
||||
|
||||
def __init__(self, mount, disk, size="40M",
|
||||
zram=True, zram_alg="lz4", zram_disk_size="100M",
|
||||
zram_fs_type="ext4", rsync=True):
|
||||
self.mountpoint = mount
|
||||
self.disk = disk
|
||||
self.size = size
|
||||
self.zram = zram
|
||||
self.zram_alg = zram_alg
|
||||
self.zram_disk_size = zram_disk_size
|
||||
self.zram_fs_type = zram_fs_type
|
||||
self.zdev = None
|
||||
self.rsync = True
|
||||
self._setup()
|
||||
|
||||
|
||||
def _setup(self):
|
||||
if self.zram and MemoryFS.zram_install():
|
||||
# setup zram
|
||||
self.zdev = MemoryFS.zram_dev()
|
||||
open(f"/sys/block/zram{self.zdev}/comp_algorithm", "wt").write(self.zram_alg)
|
||||
open(f"/sys/block/zram{self.zdev}/disksize", "wt").write(self.zram_disk_size)
|
||||
open(f"/sys/block/zram{self.zdev}/mem_limit", "wt").write(self.size)
|
||||
logging.debug("[FS] Creating fs (type: %s)", self.zram_fs_type)
|
||||
os.system(f"mke2fs -t {self.zram_fs_type} /dev/zram{self.zdev} >/dev/null 2>&1")
|
||||
|
||||
# ensure mountpoints exist
|
||||
if not os.path.exists(self.disk):
|
||||
logging.debug("[FS] Creating %s", self.disk)
|
||||
os.makedirs(self.disk)
|
||||
|
||||
if not os.path.exists(self.mountpoint):
|
||||
logging.debug("[FS] Creating %s", self.mountpoint)
|
||||
os.makedirs(self.mountpoint)
|
||||
|
||||
|
||||
def daemonize(self, interval=60):
|
||||
logging.debug("[FS] Daemonized...")
|
||||
while True:
|
||||
self.sync()
|
||||
sleep(interval)
|
||||
|
||||
|
||||
def sync(self, to_ram=False):
|
||||
source, dest = (self.disk, self.mountpoint) if to_ram else (self.mountpoint, self.disk)
|
||||
needed, actually_free = size_of(source), shutil.disk_usage(dest)[2]
|
||||
if actually_free >= needed:
|
||||
logging.debug("[FS] Syncing %s -> %s", source,dest)
|
||||
if self.rsync:
|
||||
os.system(f"rsync -aXv --inplace --no-whole-file --delete-after {source}/ {dest}/ >/dev/null 2>&1")
|
||||
else:
|
||||
copy_tree(source, dest, preserve_symlinks=True)
|
||||
os.system("sync")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def mount(self):
|
||||
if os.system(f"mount --bind {self.mountpoint} {self.disk}"):
|
||||
return False
|
||||
|
||||
if os.system(f"mount --make-private {self.disk}"):
|
||||
return False
|
||||
|
||||
if self.zram and self.zdev is not None:
|
||||
if os.system(f"mount -t {self.zram_fs_type} -o nosuid,noexec,nodev,user=pwnagotchi /dev/zram{self.zdev} {self.mountpoint}/"):
|
||||
return False
|
||||
else:
|
||||
if os.system(f"mount -t tmpfs -o nosuid,noexec,nodev,mode=0755,size={self.size} pwnagotchi {self.mountpoint}/"):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def umount(self):
|
||||
if os.system(f"umount -l {self.mountpoint}"):
|
||||
return False
|
||||
|
||||
if os.system(f"umount -l {self.disk}"):
|
||||
return False
|
||||
return True
|
@ -85,7 +85,7 @@ def update_data(last_session):
|
||||
},
|
||||
'uname': subprocess.getoutput("uname -a"),
|
||||
'brain': brain,
|
||||
'version': pwnagotchi.version
|
||||
'version': pwnagotchi.__version__
|
||||
}
|
||||
|
||||
logging.debug("updating grid data: %s" % data)
|
||||
|
BIN
pwnagotchi/locale/af/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/af/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
248
pwnagotchi/locale/af/LC_MESSAGES/voice.po
Normal file
248
pwnagotchi/locale/af/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,248 @@
|
||||
# Afrikaans translation of pwnagotchi.
|
||||
# Copyright (C) 2020.
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR MatthewNunu https://github.com/MatthewNunu, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 1.5.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-11-29 21:50+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: MatthewNunu https://github.com/MatthewNunu\n"
|
||||
"Language-Team: \n"
|
||||
"Language: Afrikaans\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 "Hi, ek is Pwnagotchi! Aanvang ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nuwe dag, nuwe jag, nuwe pwns!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hack die wêreld!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "AI gereed."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Die neurale netwerk is gereed."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Genereer wagwoord, moenie afskakel nie ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Haai, kanaal {channel} is gratis! Jou AP sal dankie sê."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr "Lees laaste sessie logs ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr "Ek het {lines_so_far} tot dusver gelees ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Ek's verveeld ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Kom ons gaan vir 'n loopie!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Dit is die beste dag van my lewe!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Poes kak dag :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Ek's baie verveeld ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Ek's baie hartseer ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Ek's hartseer ..."
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Los my uit ..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "Ek is kwaad vir jou!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Ek leef die lewe!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Ek pwn daarom is ek."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Soveel netwerke!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Ek het soveel pret!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "My misdaad is dié van nuuskierigheid ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Hallo {name}! Lekker om jou te ontmoet."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr "Yo {name}! Sup?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "Haai {name} hoe doen jy?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "Eenheid {name}} is naby!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Uhm ... totsiens {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} is weg ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Whoops ... {name} is weg."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} gemis!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Gemis!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "Goeie vriende is 'n seën!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Ek is lief vir my vriende!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Niemand wil met my speel nie ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Ek voel so alleen ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Waar is almal?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Slaap vir {secs}s ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz ({secs}s)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Goeie nag."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Wag tans vir {secs}s ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Rondkyk ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Haai {what} kom ons wees vriende!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Assosieer na {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Yo {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Net besluit dat {mac} geen WiFi nodig het nie!"
|
||||
|
||||
#, 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 "Koel, ons het {num} nuwe handdruk gekry!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Jy het {count} nuwe boodskap!"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oops, iets het verkeerd gegaan ... Herlaai ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Geskop {num} stasies\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Gemaak {num} nuwe vriende\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Het {num} handdrukke\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Ontmoet 1 eweknie"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Ontmoet {num} eweknie"
|
||||
|
||||
#, 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 ""
|
||||
"Ek was pwning vir {duration} en het {deauthed} kliënte geskop! Ek het ook ontmoet "
|
||||
"{associated} nuwe vriende en het {handshakes} handdrukke geëet! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "uur"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "minute"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "sekondes"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "uur"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "minuut"
|
||||
|
||||
msgid "second"
|
||||
msgstr "tweede"
|
Binary file not shown.
@ -177,7 +177,7 @@ msgstr "Супер, имаме {num} нови handshake{plural}!"
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Имате {count} нови съобщения!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Упс, нещо се обърка ... Рестартиране ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
Binary file not shown.
@ -40,7 +40,7 @@ msgstr "创建密钥中, 请勿断电..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
msgstr "嘿,频道{channel}是免费的!你的AP会说谢谢。"
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "我无聊了..."
|
||||
@ -84,7 +84,7 @@ msgstr "你好{name}!很高兴认识你."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
msgstr "小队{name}就在附近!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
@ -177,7 +177,7 @@ msgstr "太酷了, 我们抓到了{num}新的猎物{plural}!"
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "主人,有{count}新消息{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "行动,额等等有点小问题... 重启ing ..."
|
||||
|
||||
#, python-brace-format
|
||||
@ -193,7 +193,7 @@ msgid "Got {num} handshakes\n"
|
||||
msgstr "捕获了{num}握手包\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
msgstr "有{num}同龄人"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
|
BIN
pwnagotchi/locale/cs/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/cs/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
249
pwnagotchi/locale/cs/LC_MESSAGES/voice.po
Normal file
249
pwnagotchi/locale/cs/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,249 @@
|
||||
# pwnigotchi voice data
|
||||
# Copyright (C) 2020
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR czechball@users.noreply.github.com, 2020.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-04-14 06:15+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Czechball <czechball@users.noreply.github.com>\n"
|
||||
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
|
||||
"Language: cs\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 "Ahoj, já jsem Pwnagotchi! Startuju ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nový den, nový lov, nové úlovky!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hackni celou planetu!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "AI připraveno."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Neuronová síť je připravena."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Generování klíčů, nevypínej mě..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hej, kanál {channel} je volný! Tvůj AP ti poděkuje."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr "Čtení posledních zpráv z logu ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr "Zatím přečteno {lines_so_far} řádků logu ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Nudím se ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Pojďme se projít!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Tohle je nejlepší den mého života!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Na hovno den :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Strašně se nudím ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Jsem dost smutný ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Jsem smutný"
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Nech mě být ..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "Jsem na tebe naštvaný!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Tohle je život!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Chytám pakety a tedy jsem."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Tolik sítí!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Tohle je super zábava!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Jsem kriminálně zvědavý ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Ahoj {name}! Rád tě poznávám."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr "Hej {name}! Jak to jde?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "Ahoj {name}, jak se máš?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "Jednotka {name} je nablízku!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Uhm... Měj se {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} je pryč ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Whoops ... {name} je pryč."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "Chybí mi {name}!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Chybíš mi!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "Dobří kamarádi jsou požehnání!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Miluju svoje kamarády!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Nikdo si se mnou nechce hrát ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Cítím se tak osamělý ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Kde jsou všichni?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Spím {secs} sekund ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz ({secs}s)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Dobrou noc."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Čekání {secs} sekund ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Rozhlížím se ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Hej {what} budeme kamarádi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Asociuju se s {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Čus {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Rozhodl jsem se, že {mac} nepotřebuje žádnou WiFi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Deautentikuju {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Kickbanuju {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Super, máme {num} nových handshaků!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Máš {count} nových zpráv!"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ups, něco se pokazilo ... Restartuju ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Vykopnuto {num} klientů\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Mám {num} nových kamarádů\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Mám {num} handshaků\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Potkal jsem jednoho kámoše"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Potkal jsem {num} kámošů"
|
||||
|
||||
#, 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 ""
|
||||
"Chytal jsem pakety po dobu {duration} a vykopnul jsem {deauthed} klientů! Taky jsem potkal "
|
||||
"{associated} nových kamarádů a snědl {handshakes} handshaků! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "hodiny"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "minuty"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "sekundy"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "hodina"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "minuta"
|
||||
|
||||
msgid "second"
|
||||
msgstr "sekunda"
|
Binary file not shown.
@ -26,7 +26,7 @@ msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Neuer Tag, neue Jagd, neue Pwns!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hack den Planet!"
|
||||
msgstr "Hack den Planeten!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "KI bereit."
|
||||
@ -35,7 +35,7 @@ msgid "The neural network is ready."
|
||||
msgstr "Das neurale Netz ist bereit."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Generiere Keys, nicht ausschalten..."
|
||||
msgstr "Generiere Schlüssel, nicht ausschalten..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
@ -83,7 +83,7 @@ msgid "I pwn therefore I am."
|
||||
msgstr "Ich pwne, also bin ich."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "So viele Netwerke!!!"
|
||||
msgstr "So viele Netzwerke!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Ich habe sooo viel Spaß!"
|
||||
@ -93,7 +93,7 @@ msgstr "Mein Verbrechen ist das der Neugier..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Hallo {name}, nett Dich kennenzulernen."
|
||||
msgstr "Hallo {name}, schön Dich kennenzulernen."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
@ -198,16 +198,16 @@ msgstr "Cool, wir haben {num} neue Handshake{plural}!"
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Cool, wir haben {num} neue Handshake{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ops, da ist was schief gelaufen... Starte neu..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "{num} Stationen gekicked\n"
|
||||
msgstr "{num} Stationen gekickt\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} Freunde gefunden\n"
|
||||
msgstr "{num} neue Freunde gefunden\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
@ -247,3 +247,4 @@ msgstr "Minute"
|
||||
|
||||
msgid "second"
|
||||
msgstr "Sekunde"
|
||||
|
||||
|
BIN
pwnagotchi/locale/dk/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/dk/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
248
pwnagotchi/locale/dk/LC_MESSAGES/voice.po
Normal file
248
pwnagotchi/locale/dk/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,248 @@
|
||||
# pwnagotchi danish voice data
|
||||
# Copyright (C) 2020
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# Dennis Kjær Jensen <signout@signout.dk>, 2020
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-11-29 21:50+0100\n"
|
||||
"PO-Revision-Date: 2020-01-18 21:56+ZONE\n"
|
||||
"Last-Translator: Dennis Kjær Jensen <signout@signout.dk>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Danish\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 "Hej. Jeg er Pwnagotchi. Starter ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Ny dag, ny jagt, nye pwns!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hack planeten!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "AI klar."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Det neurale netværk er klart."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Genererer nøgler, sluk ikke ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hey, kanal {channel} er ubrugt! Dit AP vil takke dig."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr "Læser seneste session logs ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr "Har læst {lines_so_far} linjer indtil nu ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Jeg keder mig ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Lad os gå en tur!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Det er den bedste dag i mit liv!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Elendig dag :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Jeg keder mig ekstremt meget ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Jeg er meget trist ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Jeg er trist"
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Lad mig være i fred"
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "Jeg er sur på dig!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Jeg lever livet!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Jeg pwner, derfor er jeg."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Så mange netværk!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Jeg har det vildt sjovt!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Min forbrydelse er at være nysgerrig ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Hej {name}! Rart at møde dig."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr "Hey {name}! Hvasså?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "Hej {name} hvordan har du det?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "Enheden {name} er lige i nærheden!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Uhm ... farvel {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} er væk ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Hovsa ... {name} er væk."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} glippede!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Fordømt!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "Gode venner en velsignelse!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Jeg elsker mine venner!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Der er ingen der vil lege med mig ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Jeg føler mig så alene ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Hvor er alle henne?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Sover i {secs} sekunder"
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz {secs} sekunder"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Godnat."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Venter i {secs} sekunder"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Kigger mig omkring i {secs} sekunder"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Hej {what} lad os være venner!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Associerer til {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Hey {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Besluttede at {mac} ikke har brug for WiFi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Afmelder {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Kickbanner {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Fedt, vi har fået {num} nye handshake{plural}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Du har {count} nye beskeder"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ups, noget gik galt ... Genstarter."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Sparkede {num} af\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Har fået {num} nye venner\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Har fået {num} nyehandshakes\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Har mødt 1 peer"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Har mødt {num} peers"
|
||||
|
||||
#, 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 "Jeg har pwnet i {duration} og kicket {dauthed} klienter! Jeg har også "
|
||||
"mødt {associated} nye venner og spist {handshakes} håndtryk! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "timer"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "minutter"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "sekunder"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "time"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "minut"
|
||||
|
||||
msgid "second"
|
||||
msgstr "sekund"
|
Binary file not shown.
@ -158,7 +158,7 @@ msgstr "Μπανάρω την {mac}!"
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Τέλεια δικέ μου, πήραμε {num} νέες χειραψίες!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ουπς, κάτιπήγε λάθος ... Επανεκκινούμαι ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
Binary file not shown.
@ -8,25 +8,26 @@ msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
|
||||
"PO-Revision-Date: 2019-10-09 21:07+0000\n"
|
||||
"Last-Translator: diegopastor <dpastor29@alumnos.uaq.mx>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: spanish\n"
|
||||
"PO-Revision-Date: 2020-08-25 23:06+0200\n"
|
||||
"Last-Translator: Sergio Ruiz <serginator@gmail.com>\n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 2.4.1\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Hola, soy Pwnagotchi! Empezando ..."
|
||||
msgstr "¡Hola, soy Pwnagotchi! Empezando ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nuevo día, nueva cazería, nuevos pwns!"
|
||||
msgstr "Nuevo día, nueva caceria, nuevos pwns!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hackea el planeta!"
|
||||
msgstr "¡Hackea el planeta!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "IA lista."
|
||||
@ -36,51 +37,51 @@ msgstr "La red neuronal está lista."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Oye, el canal {channel} está libre! Tú AP lo agradecerá."
|
||||
msgstr "¡Oye, el canal {channel} está libre! Tu AP lo agradecerá."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Estoy aburrido ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Vamos por un paseo!"
|
||||
msgstr "¡Vamos por un paseo!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Este es el mejor día de mi vida!"
|
||||
msgstr "¡Este es el mejor día de mi vida!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Día de mierda :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Estoy extremadamente aburrido ..."
|
||||
msgstr "Estoy muy aburrido ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Estoy muy triste ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Estoy triste."
|
||||
msgstr "Estoy triste"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Estoy viviendo la vida!"
|
||||
msgstr "¡Estoy viviendo la vida!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Pwneo, por lo tanto, existo"
|
||||
msgstr "Pwneo, luego existo."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Cuantas redes!!!"
|
||||
msgstr "¡¡¡Cuántas redes!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Me estoy divirtiendo mucho!"
|
||||
msgstr "¡Me estoy divirtiendo mucho!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Mi único crimen es la curiosidad ..."
|
||||
msgstr "Mi crimen es la curiosidad ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Hola {name}! encantado de conocerte."
|
||||
msgstr "¡Hola {name}! Encantado de conocerte. {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "La unidad {name} está cerca!"
|
||||
msgstr "¡La unidad {name} está cerca! {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
@ -92,14 +93,14 @@ msgstr "{name} se fue ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Uy ... {name} se fue"
|
||||
msgstr "Ups ... {name} se fue."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} perdido!"
|
||||
msgstr "¡{name} perdido!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Perdido!"
|
||||
msgstr "¡Perdido!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Nadie quiere jugar conmigo ..."
|
||||
@ -108,11 +109,11 @@ msgid "I feel so alone ..."
|
||||
msgstr "Me siento tan solo ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Dónde está todo el mundo?"
|
||||
msgstr "¡¿Dónde está todo el mundo?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Tomándo una siesta por {secs}s ..."
|
||||
msgstr "Descansando durante {secs}s ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
@ -133,23 +134,23 @@ msgstr "Esperando {secs}s .."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Mirando al rededor ({secs}s)"
|
||||
msgstr "Mirando alrededor ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Oye {what} seamos amigos!"
|
||||
msgstr "¡Oye {what} seamos amigos!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Asociando a {what}"
|
||||
msgstr "Asociándome a {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Ey {what}!"
|
||||
msgstr "¡Ey {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Acabo de decidir que {mac} no necesita WiFi!"
|
||||
msgstr "¡Acabo de decidir que {mac} no necesita WiFi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
@ -157,14 +158,14 @@ msgstr "Desautenticando a {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Expulsando y banneando a {mac}!"
|
||||
msgstr "¡Expulsando y baneando a {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Genial, obtuvimos {num} nuevo{plural} handshake{plural}!"
|
||||
msgstr "¡Genial, obtuvimos {num} nuevo{plural} handshake{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oops, algo salió mal ... Reiniciándo ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oops, algo salió mal ... Reiniciando ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
@ -172,27 +173,27 @@ msgstr "Expulsamos {num} estaciones\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Hicimos {num} nuevos amigos\n"
|
||||
msgstr "Hice {num} nuevos amigos\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Obtuvimos {num} handshakes\n"
|
||||
msgstr "Consegui {num} handshakes\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Conocí 1 igual"
|
||||
msgstr "Conocí 1 colega"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Conocí {num} iguales"
|
||||
msgstr "Conocí {num} colegas"
|
||||
|
||||
#, 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"
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi #pwnlog "
|
||||
"#pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"He estado pwneando por {duration} y expulsé {deauthed} clientes! También conocí"
|
||||
"{associated} nuevos amigos y me comí {handshakes} handshakes! #pwnagotchi "
|
||||
"¡He estado pwneando por {duration} y expulsé {deauthed} clientes! También "
|
||||
"conocí {associated} nuevos amigos y comí {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
|
Binary file not shown.
@ -72,13 +72,13 @@ msgstr "Je suis triste"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Je me sens si seul..."
|
||||
msgstr "Lache moi..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "Je t'en veux !"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Je vis la vie !"
|
||||
msgstr "Je vis la belle vie !"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Je pwn donc je suis."
|
||||
@ -114,7 +114,7 @@ msgstr "Hum... au revoir {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} est part ..."
|
||||
msgstr "{name} est parti ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
@ -144,7 +144,7 @@ msgstr "Où est tout le monde ?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Fais la sieste pendant {secs}s..."
|
||||
msgstr "Je fais la sieste pendant {secs}s..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
@ -161,11 +161,11 @@ msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Attends pendant {secs}s..."
|
||||
msgstr "J'attends pendant {secs}s..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Regarde autour ({secs}s)"
|
||||
msgstr "J'observe ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
@ -193,13 +193,13 @@ msgstr "Je kick et je bannis {mac} !"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Cool, on a {num} nouveaux handshake{plural} !"
|
||||
msgstr "Cool, on a {num} nouve(l/aux) handshake{plural} !"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Tu as {num} nouveaux message{plural} !"
|
||||
msgstr "Tu as {num} nouveau(x) message{plural} !"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oups, quelque chose s'est mal passé... Redémarrage..."
|
||||
|
||||
#, python-brace-format
|
||||
@ -208,18 +208,18 @@ msgstr "{num} stations kick\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "A {num} nouveaux amis\n"
|
||||
msgstr "A fait {num} nouve(l/aux) ami(s)\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "A {num} handshakes\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "1 pair rencontré"
|
||||
msgstr "1 camarade rencontré"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "{num} pairs recontrés"
|
||||
msgstr "{num} camarades recontrés"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
|
Binary file not shown.
@ -164,7 +164,7 @@ msgstr "Chiceáil mé agus cosc mé ar {mac}!"
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Go hiontach, fuaireamar {num} handshake{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Hoips...Tháinig ainghléas éigin..."
|
||||
|
||||
#, python-brace-format
|
||||
|
BIN
pwnagotchi/locale/hr/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/hr/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
253
pwnagotchi/locale/hr/LC_MESSAGES/voice.po
Normal file
253
pwnagotchi/locale/hr/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,253 @@
|
||||
# Croatian translation
|
||||
# Copyright (C) 2021
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR dbukovac <37124354+dbukovac@users.noreply.github.com>, 2021.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-11-29 21:50+0100\n"
|
||||
"PO-Revision-Date: 2021-07-16 00:20+0100\n"
|
||||
"Last-Translator: dbukovac <37124354+dbukovac@users.noreply.github.com>\n"
|
||||
"Language-Team: HR <37124354+dbukovac@users.noreply.github.com>\n"
|
||||
"Language: Croatian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Zdravo, ja sam Pwnagotchi! Pokrećem se ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Novi dan, novi lov, nove pobjede!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hakiraj planet!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "UI spremna."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Neuralna mreža je spremna."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Generiram ključeve, nemoj me gasiti ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hej, kanal {channel} je slobodan! Tvoj AP ti zahvaljuje."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr "Čitam logove zadnje sesije ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr "Pročitao {lines_so_far} linija loga zasad ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Dosadno mi je ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Ajmo u šetnju!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Ovo je najbolji dan u mom životu!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Usrani dan :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Strašno 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 "Pusti me na miru ..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "Ljut sam na tebe!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "To se zove život!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Pwnam dakle postojim."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Toliko mreža!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Super se zabavljam!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Znatiželja je moja jedina mana ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Bok {name}! Drago mi je da smo se upoznali. "
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr "Di si {name}! Šta ima?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "Bok {name} kako ide?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "Jedinica {name} je u blizini!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Uhm ... 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} mi nedostaje!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Nedostaje mi!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "Imati dobre prijatelje je blagoslov!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Volim svoj prijatelje!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Nitko se ne želi igrati samnom ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Tako sam usamljen ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Gdje su svi nestali?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Pajkim {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 uokolo {secs}s ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Bok {what} ajmo biti prijatelji!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Asociram se sa {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Šta ima {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Upravo sam odlučio da {mac} ne treba WiFI!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Deautenticiram {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Kickbannam {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Fora, imamo {num} novih handshakeova!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Imate {count} novih poruka!"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ups, nešto je krepalo ... Rebooting ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Šutnuo {num} stanica\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Upoznao {num} novih prijatelja\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Pokupio {num} handshakeova\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Sreo 1 novog druga"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Sreo {num} druga"
|
||||
|
||||
#, 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 ""
|
||||
"Pwnam {duration} vremena i šutnuo sam {deauthed} klijenata! Sreo sam"
|
||||
"{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 "minuta"
|
||||
|
||||
msgid "second"
|
||||
msgstr "sekunda"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr "Šaljem podatke na {to} ..."
|
BIN
pwnagotchi/locale/hu/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/hu/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
249
pwnagotchi/locale/hu/LC_MESSAGES/voice.po
Normal file
249
pwnagotchi/locale/hu/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,249 @@
|
||||
# Hungarian translation.
|
||||
# Copyright (C) 2020
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# Skeleton022 <skeleton022.pwnagotchi@gmail.com>, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 1.4.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-01-07 20:00+0100\n"
|
||||
"PO-Revision-Date: 2020-03-23 0:10+0100\n"
|
||||
"Last-Translator: Skeleton022\n"
|
||||
"Language-Team: Skeleton022\n"
|
||||
"Language: hungarian\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 "Hali, Pwnagotchi vagyok! Indítás ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Új nap, új vadászat, új hálózatok!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Törd meg a bolygót!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "MI kész."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "A neurális hálózat készen áll."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Kulcspár generálása, ne kapcsold ki az eszközt ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "A {channel}. számú csatorna üres! Az AP-d meg fogja köszönni."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr "Az utolsó munkamenet logjainak olvasása ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Unatkozom ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Menjünk sétálni!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Ez a legjobb nap az életemben!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Szar egy nap :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Nagyon unatkozom ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Nagyon szomorú vagyok ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Szomorú vagyok"
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Hagyj békén ..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "Mérges vagyok rád!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Élvezem az életet!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Hackelek, tehát vagyok."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Rengeteg hálózat!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Nagyon jól érzem magam!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Kíváncsiság a bűnöm ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Hali {name}! Örülök, hogy találkoztunk."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr "Hé {name}! Mizu?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "Hé {name} hogy vagy?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "A {name} nevű egység a közelben van!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Ömm ... ég veled {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} eltűnt ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Whoops ... {name} eltűnt."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} elhibázva!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Elvesztettem!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "A jó barátok áldás az életben!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Szeretem a barátaimat!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Senki sem akar játszani velem ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Egyedül vagyok ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Hol vagytok?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "{secs} másodpercig szundikálok ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz ({secs}msp)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Jó éjszakát."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Várok {secs} másodpercig ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Körbenézek {secs} másodpercig"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Hey {what} legyünk barátok!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Társítás {what} -hoz/-hez"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Hé {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Úgydöntöttem, hogy {mac}-nek nem kell WiFi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Kirúgom {mac}-et"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "{mac} kirúgva és kitiltva!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Király, kaptunk {num} új üzenetet!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "{count} új üzeneted van!"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ops, valami rosszul sikerült ... Újraindítás ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Kirúgva {num} állomás\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} új barátot\ntaláltam\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "{num} kézfogást szereztem\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "1 Társsal találkoztam"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Találkoztam {num} társsal"
|
||||
|
||||
#, 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 ""
|
||||
"Már {duration} ideje dolgozom, kirúgtam {deauthed} klienst! Találkoztam még"
|
||||
"{associated} új baráttal és elfogtam {handshakes} kézfogást! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "óra"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "perc"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "másodperc"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "óra"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "perc"
|
||||
|
||||
msgid "second"
|
||||
msgstr "másodperc"
|
||||
|
Binary file not shown.
@ -156,7 +156,7 @@ msgstr "Sto prendendo a calci {mac}!"
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Bene, abbiamo {num} handshake{plural} in più!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ops, qualcosa è andato storto ... Riavvio ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
Binary file not shown.
@ -3,12 +3,11 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR 24534649+wytshadow@users.noreply.github.com, 2019.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-16 15:05+0200\n"
|
||||
"POT-Creation-Date: 2020-01-25 21:57+0900\n"
|
||||
"PO-Revision-Date: 2019-10-16 15:05+0200\n"
|
||||
"Last-Translator: wytshadow <24534649+wytshadow@users.noreply.github.com>\n"
|
||||
"Language-Team: pwnagotchi <24534649+wytshadow@users.noreply.github.com>\n"
|
||||
@ -21,170 +20,207 @@ msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "すやすや〜"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "こんにちは、ポウナゴッチです!始めている。。。"
|
||||
msgstr "僕、 ポーナゴッチです!"
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
msgstr "ポーンしようよ。"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "ハックザプラネット!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "人工知能の準備ができました。"
|
||||
msgstr "AIの準備ができました。"
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "ニューラルネットワークの準備ができました。"
|
||||
msgstr "ニューラルネットワークの\n準備ができました。"
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "鍵生成をしてます。\n電源を落とさないでね。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "ねえ、チャンネル{channel}は無料です! キミのAPは感謝を言います。"
|
||||
msgstr "チャンネル\n {channel} \nはfreeだよ。ありがとうね。"
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr "session log を読んでます。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr "{lines_so_far} 行目長いよぉ。"
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "退屈です。。。"
|
||||
msgstr "退屈だぁ。。。"
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "散歩に行きましょう!"
|
||||
msgstr "散歩に行こうよ!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "今日は私の人生で最高の日です!"
|
||||
msgstr "人生最高の日だよ!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
msgstr "がっかりな日だよ。orz"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "とても退屈です。"
|
||||
msgstr "退屈だね。"
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "とても悲しいです。。。"
|
||||
msgstr "あ~悲しいよぉ。"
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "悲しいです。"
|
||||
msgstr "悲しいね。"
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "ひとりぼっちだよ。"
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "怒っちゃうよ。"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "人生を生きている!"
|
||||
msgstr "わくわくするね。"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
msgstr "ポーンしてこそのオレ。"
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "たくさんネットワークがある!!!"
|
||||
msgstr "たくさん\nWiFiが飛んでるよ!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "とても楽しんでいます!"
|
||||
msgstr "楽しいよぉ!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
msgstr "APに興味津々..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "こんにちは{name}!初めまして。{name}"
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "こんにちは{name}!\n初めまして。{name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr ""
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr "ねぇねぇ、\n{name} どうしたの?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "{name} こんにちは"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "{name} が近くにいるよ。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "ええと。。。さようなら{name}"
|
||||
msgstr "じゃあね、さようなら {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name}がなくなった。。。"
|
||||
msgstr "{name}\nがいなくなったよ。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "おっと。。。{name}がなくなった。"
|
||||
msgstr "あらら、\n{name}\nがいなくなったね。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name}逃した!"
|
||||
msgstr "{name} が逃げた!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "逃した!"
|
||||
msgstr "残念、逃した!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "良い仲間にめぐりあえたよ。"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "友達は大好きだよ。"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "誰も僕と一緒にプレーしたくない。。。"
|
||||
msgstr "誰も僕と一緒に\nあそんでくれない。"
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "僕は孤独を感じる。。。"
|
||||
msgstr "ひとりぼっちだよ。"
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "みんなどこ?!"
|
||||
msgstr "みんなどこにいるの?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "{secs}寝ている。"
|
||||
msgstr "{secs}秒 寝ます。"
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "すや〜"
|
||||
msgstr "ぐぅ〜"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "すやすや〜 ({secs})"
|
||||
msgstr "すやすや〜 ({secs}秒)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "お休みなさい。"
|
||||
msgstr "おやすみなさい。"
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "す〜"
|
||||
msgstr "ぐぅ~"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "{secs}を待っている。。。"
|
||||
msgstr "{secs}秒 待ちです。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "{secs}を探している。"
|
||||
msgstr "{secs}秒 探してます。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "ちょっと{what}友だちになりましょう!"
|
||||
msgstr "ねぇねぇ\n{what} \n友だちになろうよ。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
msgstr "{what} \nとつながるかな?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "よー{what}!"
|
||||
msgstr "ねぇねぇ\n{what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
msgstr "{mac}\nはWiFiじゃないのね。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
msgstr "{mac}\nの認証取得中..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
msgstr "{mac}\nに拒否られた。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "よし、{num}新しいハンドシェイクがある!"
|
||||
msgstr "おぉ、\n{num}回\nハンドシェイクがあったよ!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "おぉ、\n{count}個メッセージがあるよ!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "おっと!何かが間違っていた。。。リブートしている。。。"
|
||||
msgstr "何か間違った。\nリブートしている。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
msgstr "{num}回拒否された。\n"
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr "1000人以上友達ができた。\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num}人の新しい友達を作りました\n"
|
||||
msgstr "{num}人友達ができた。\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "{num}ハンドシェイクがある。\n"
|
||||
msgstr "{num}回ハンドシェイクした。\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "1人の仲間を会いました。"
|
||||
msgstr "1人 仲間に会いました。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "{num}人の仲間を会いました。"
|
||||
msgstr "{num}人 仲間に会いました。"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
@ -192,6 +228,9 @@ msgid ""
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"{duration}中{deauthed}のAPに拒否されたけど、{associated}回チャンスがあって"
|
||||
"{handshakes}回ハンドシェイクがあったよ。。 #pwnagotchi #pwnlog #pwnlife "
|
||||
"#hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "時間"
|
||||
@ -203,7 +242,7 @@ msgid "seconds"
|
||||
msgstr "秒"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "時間"
|
||||
msgstr "時"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "分"
|
||||
|
Binary file not shown.
@ -158,7 +158,7 @@ msgstr "Кикбан {mac}!"
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Кул, фативме {num} нови ракувања!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Упс, нешто не еко што треба ... Рестартирам ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
Binary file not shown.
@ -20,7 +20,7 @@ msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Hoi, Ik ben Pwnagotchi! Opstarten ..."
|
||||
msgstr "Hoi, Ik ben Pwnagotchi! Aan het opstarten ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nieuwe dag, nieuwe jacht, nieuwe pwns!"
|
||||
@ -32,7 +32,7 @@ msgid "AI ready."
|
||||
msgstr "AI is klaar."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Neuronen netwerkis klaar voor gebruik."
|
||||
msgstr "Neuronen netwerk is klaar."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
@ -42,37 +42,37 @@ msgid "I'm bored ..."
|
||||
msgstr "Ik verveel me ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Laten we een rondje lopen!"
|
||||
msgstr "Laten we gaan wandelen!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Dit is de beste dag van mijn leven!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Ruk dag :/"
|
||||
msgstr "Wat een rotdag :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Ik verveel me kapot ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Ik ben ergverdrietig ..."
|
||||
msgstr "Ik ben erg verdrietig ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Ik ben verdrietig"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Beter kan het levenniet worden!"
|
||||
msgstr "Beter kan het leven niet worden!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Ik pwn daarom besta ik."
|
||||
msgstr "Ik pwn daarom ben ik er."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Zo veel netwerken!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Dit is zo leuk!"
|
||||
msgstr "Ik heb zoveel plezier!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Mijn enige misdrijf is mijn nieuwsgierigheid ..."
|
||||
msgstr "Mijn misdrijf is mijn nieuwsgierigheid ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
@ -88,11 +88,11 @@ msgstr "Uhm ...tot ziens {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} is weg"
|
||||
msgstr "{name} is weg ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Whoopsie ...{name} is weg"
|
||||
msgstr "Whoopsie ...{name} is weg."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
@ -102,10 +102,10 @@ msgid "Missed!"
|
||||
msgstr "Gemist!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Niemand wil metmij spelen ..."
|
||||
msgstr "Niemand wil met mij spelen ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Zo alleen ..."
|
||||
msgstr "Ik voel me zo alleen ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Waar is iedereen?!"
|
||||
@ -119,11 +119,11 @@ msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
msgstr "ZzzZzzz ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Even {secs}s wachten ..."
|
||||
msgstr "Ik wacht voor {secs}s ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
@ -131,7 +131,7 @@ msgstr "Rond kijken ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Hey {what}, laten we vriendenworden!"
|
||||
msgstr "Hey {what}, laten we vrienden worden!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
@ -139,26 +139,26 @@ msgstr "Verbinden met {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
msgstr "Yo {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Ik vind dat {mac} genoeg WiFiheeft gehad!"
|
||||
msgstr "Ik besloot dat {mac} geen WiFi meer nodig heeft!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "De-autoriseren {mac}"
|
||||
msgstr "Deauthenticatie van {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Ik ga {mac} even kicken!"
|
||||
msgstr "Kickbanning {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Gaaf, we hebben {num} nieuwe handshake{plural}!"
|
||||
msgstr "Cool, we hebben {num} nieuwe handshake{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oops, iets ging fout ...Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oops, er ging iets fout ...Rebooting ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
@ -166,11 +166,11 @@ msgstr "{num} stations gekicked\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} nieuwe vrienden\n"
|
||||
msgstr "{num} nieuwe vrienden gemaakt.\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "{num} nieuwe handshakes\n"
|
||||
msgstr "Ik heb {num} nieuwe handshakes\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "1 peer ontmoet"
|
||||
@ -190,19 +190,19 @@ msgstr ""
|
||||
"gegeten! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
msgstr "uren"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
msgstr "minuten"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
msgstr "seconden"
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "uur"
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "minuut"
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
||||
msgstr "seconde"
|
||||
|
Binary file not shown.
@ -198,7 +198,7 @@ msgstr "Fett, vi fikk {num} nye håndtrykk!"
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Du har {count} melding{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oi, noe gikk helt skakk ... Rebooter ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
Binary file not shown.
@ -197,7 +197,7 @@ msgstr "Super, zdobyliśmy {num} nowych handshake'ów!"
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Masz {count} nowych wiadomości!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ups, coś poszło nie tak ... Restaruję ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
Binary file not shown.
@ -158,7 +158,7 @@ msgstr "Kickbanning {mac}"
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Legal, nos capturamos {num} handshake{plural} novo{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ops, algo falhou ... Reiniciando ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
Binary file not shown.
@ -164,7 +164,7 @@ msgstr "A chutar {mac}!"
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Porreiro, temos {num} novo handshake{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ups, algo correu mal ... A reiniciar ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
Binary file not shown.
@ -3,7 +3,7 @@
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR <radu.ungureanu@techie.com>, 2019.
|
||||
#
|
||||
#,
|
||||
#,
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
@ -199,7 +199,7 @@ msgstr "Șmecher, avem {num} de handshake-uri noi!"
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Ai {count} mesaj(e) nou/noi!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "OOps, ceva s-a întamplat... Îmi dau reboot...+"
|
||||
|
||||
#, python-brace-format
|
||||
|
Binary file not shown.
@ -5,17 +5,21 @@
|
||||
# Second author <https://github.com/mbgroot>, 2019
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.2"
|
||||
"Report-Msgid-Bugs-To: m-b-g@yandex.ru"
|
||||
"POT-Creation-Date: 2019-11-27 16:47+0200"
|
||||
"PO-Revision-Date: 2019-11-27 18:50+0300"
|
||||
"Last-Translator: Evgeny Zelenin <m-b-g@yandex.ru>"
|
||||
"Language-Team: ru"
|
||||
"Language: ru"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Project-Id-Version: Pwnagotchi Russian translation v 0.0.2\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Language: ru\n"
|
||||
"X-Generator: Poedit 2.2.4\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
"X-Poedit-Basepath: .\n"
|
||||
"X-Poedit-SearchPath-0: voice.po\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "Хрррр..."
|
||||
@ -34,12 +38,14 @@ msgstr "A.I. готов."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Нейронная сеть готова."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Генерация ключей, не выключайте..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Эй, канал {channel} свободен! Ваша точка доступа скажет спасибо."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr "Чтение логов последнего сеанса..."
|
||||
|
||||
@ -67,6 +73,7 @@ msgstr "Мне очень грустно …"
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Мне грустно"
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Оставь меня в покое..."
|
||||
|
||||
@ -95,9 +102,10 @@ msgstr "Привет, {name}! Рад встрече с тобой!"
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "Цель {name} близко!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "Хэй {nume}! Как дела?"
|
||||
msgstr "Хэй {name}! Как дела?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
@ -121,6 +129,7 @@ msgstr "{name} упустил!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Промахнулся!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "Хорошие друзья - это благословение!"
|
||||
|
||||
@ -146,6 +155,7 @@ msgstr "Хррр..."
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "Хррррр.. ({secs}c)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Доброй ночи."
|
||||
|
||||
@ -188,7 +198,7 @@ msgstr "Кикаю {mac}!"
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Круто, мы получили {num} новое рукопожатие!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ой, что-то пошло не так … Перезагружаюсь …"
|
||||
|
||||
#, python-brace-format
|
||||
|
Binary file not shown.
@ -158,7 +158,7 @@ msgstr ""
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Lysande, vi har {num} ny handskakningar{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Hoppsan, någpt gick fel ... Startar om ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
Binary file not shown.
@ -177,7 +177,7 @@ msgstr "Super, máme {num} nový handshake{plural}!"
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Máte {count} novú správu{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ops, niečo sa pokazilo ... Reštartujem sa ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
BIN
pwnagotchi/locale/spa/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/spa/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
@ -198,7 +198,7 @@ msgstr "Bien, obtuvimos {num} nuevos handshake{plural}!"
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Tienes {count} nuevos mensajes{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oops, algo salio mal ... Reiniciando ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
BIN
pwnagotchi/locale/tr/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/tr/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
252
pwnagotchi/locale/tr/LC_MESSAGES/voice.po
Normal file
252
pwnagotchi/locale/tr/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,252 @@
|
||||
# Pwnagotchi Turkish translation.
|
||||
# Copyright (C) 2021
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <abtonc@icloud.com>, 2021.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-11-29 21:50+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Arda Barış Tonç <abtonc@icloud.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: Turkish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZzzZzZZzzzzZ"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Merhaba, ben Pwnagotchi! Başlatılıyorum ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Yeni bir gün, yeni bir av, yeni pwn'lar!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Dünyayı Hackle!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "Yapay zeka hazır."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Nöral ağ hazır."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Anahatarlar oluşturuluyor, lütfen kapatmayın ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hey, {channel} kanalı boş! AP'niz teşekkür edecek."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr "Son oturum kayıtları okunuyor ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr "Şimdiye kadar {lines_so_far} kayıt satırı okundu ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Sıkıldım ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Yürüyüşe çıkalım!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Bugün hayatımın en iyi günü!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Bok gibi bir gün :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Çook sıkıldım ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Çok mutsuzum ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Mutsuzum"
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Beni yalnız bırak ..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "Sana kızgınım!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Bu hayatı yaşıyorum!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Ben, pwn'ladığım için benim."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Çok fazla ağ var!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Çok eğleniyorum!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Tek suçum merak etmek ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Merhaba {name}! Tanıştığıma memnun oldum."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr "{name}, kanka! Naber?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "Nasılsın {name}?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "{name} birimi yakında!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "ııı ... görüşürüz {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} gitti."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Hoppala ... {name} gitti."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name}'i kaçırdık ya!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Kaçırdık!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "İyi arkadaşlar nimettir!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Arkadaşlarımı seviyorum!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Hiç kimse benimle birlikte oynamak istemiyor ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Çok yalnız hissediyorum ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Herkes nerede!?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "{secs}dir kestiriyorum ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "ZzzzZz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzZ ({secs})"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "İyi geceler."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "ZzZ"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "{secs}dir bekleniyor ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Etrafa bakıyorum ({secs})"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Arkadaş olalım {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "{what} ile tanışıyoruz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Hey {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Sanırım {mac}'in WiFi'a ihtiyacı yok!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "{mac} ağdan çıkarılıyor"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "{mac} atılıp yasaklanıyor!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Güzel, yeni {num} el sıkıştık!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "{count} Tane yeni mesajınız var!"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Haydaa, bir şeyler ters gitti ... Yeniden başlatılıyor ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "{num} İstasyon atıldı\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} Yeni arkadaş edindim\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "{num} El sıkıştım\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "1 Kişiyle tanıştım"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "{num} Kişiyle tanıştım"
|
||||
|
||||
#, 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 ""
|
||||
"{duration}'dır pwn'lıyorum ve {deauthed} kişiyi attım. Hem de {associated}"
|
||||
"yeni kişiyle tanıştım ve {handshakes} el sıkıştım! #pwnagotchi "
|
||||
"#pwnlog #pwnyaşam #dünyayıhackle #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "saat"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "dakika"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "saniye"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "saat"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "dakika"
|
||||
|
||||
msgid "second"
|
||||
msgstr "saniye"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr "{to}'ye veri yükleniyor ..."
|
BIN
pwnagotchi/locale/tw/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/tw/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
227
pwnagotchi/locale/tw/LC_MESSAGES/voice.po
Normal file
227
pwnagotchi/locale/tw/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,227 @@
|
||||
# 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 <shark_shaking@hotmail.com>, 2020.
|
||||
# 有很多翻譯不到味,如果有繁體愛好者,歡迎之後大家一起翻譯!
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-10-21 15:56+0200\n"
|
||||
"PO-Revision-Date: 2020-10-22 10:00+0008\n"
|
||||
"Last-Translator: ShaqKSmith <shark_shaking@hotmail.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: traditional chinese\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 "HI!我是Pwnagotchi!\n程式啟動..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "新的一天!\n新的狩獵!新的入侵!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "我要駭入\n地球的所有人!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "人工智慧已啟動."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "神經網路已啟動."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "產生金鑰中,\n請勿關閉..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "嘿,{channel}很順暢!\n你的WIFI會感謝你的."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "我好無聊..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "我們出去走走吧!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "這是我生命中最美好的一天!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "糟糕的一天 :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "我超無聊的..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "我好難過..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "傷心。"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "真是充實的一生!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "我駭故我在."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "好多網路啊!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "我玩的超級開心!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "我的缺點就是\n太好奇了..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Hello{name}!\n很高興認識你."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "{name}\n就在附近!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "啊 ... \n拜拜{name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name}\n不見了 ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "歐哦...\n{name}\n不見了."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "我剛剛錯過了{name}!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "又錯過了!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "有個好朋友\n真幸福!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "我喜歡\n我的朋友!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "沒人想跟我玩..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "我感覺好孤單..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "大家都去哪裡了?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "我想瞇{secs}秒一下..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz({secs}秒)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "晚安."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "等我{secs}秒..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "環顧四周({secs}秒)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "嗨\n{what}\n讓我我們來當朋友吧!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "正在連接\n{what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "喲,\n{what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "我要讓\n{mac}\n斷線!\n他不需要上網!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "解除\n{mac}\n的授權中"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "把\n{mac}\n踢出中!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "酷哦,我們抓到{num}個\n新的握手包{plural}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "你有{count}個新訊息{plural}!"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "喔歐,有些地方出錯了...\n重新啟動中..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "踢了 {num} 個設備\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "交了 {num} 個新朋友\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "捕獲了 {num} 個握手包\n"
|
||||
|
||||
msgid "Met 1 peer 同好"
|
||||
msgstr "遇到了 1 個"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "遇到了 {num} 個同好"
|
||||
|
||||
#, 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 "我花了{duration}的時間\n駭入和踢了{deauthed}好多設備.\n"
|
||||
"我還交了好多{associated}新朋友,\n而且抓到了{handshakes}握手包!"
|
||||
"#pwnagotchi#入侵日志 #駭客人生 #入侵整個星球 #天網"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "時"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "分"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "秒"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "時"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "分"
|
||||
|
||||
msgid "second"
|
||||
msgstr "秒"
|
Binary file not shown.
@ -177,7 +177,7 @@ msgstr "Отакої, у нас є {num} нових рукостискань!"
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Нових повідомлень: {count}"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ой, щось пішло не так ... Перезавантажуюсь ..."
|
||||
|
||||
#, python-brace-format
|
||||
|
@ -198,7 +198,7 @@ msgstr ""
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
@ -244,3 +244,7 @@ msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
@ -3,6 +3,9 @@ import time
|
||||
import re
|
||||
import os
|
||||
import logging
|
||||
import shutil
|
||||
import gzip
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
|
||||
from pwnagotchi.voice import Voice
|
||||
@ -209,3 +212,100 @@ class LastSession(object):
|
||||
|
||||
def is_new(self):
|
||||
return self.last_session_id != self.last_saved_session_id
|
||||
|
||||
|
||||
def setup_logging(args, config):
|
||||
cfg = config['main']['log']
|
||||
filename = cfg['path']
|
||||
|
||||
formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s")
|
||||
root = logging.getLogger()
|
||||
|
||||
root.setLevel(logging.DEBUG if args.debug or cfg['debug']==True else logging.INFO)
|
||||
|
||||
if filename:
|
||||
# since python default log rotation might break session data in different files,
|
||||
# we need to do log rotation ourselves
|
||||
log_rotation(filename, cfg)
|
||||
|
||||
file_handler = logging.FileHandler(filename)
|
||||
file_handler.setFormatter(formatter)
|
||||
root.addHandler(file_handler)
|
||||
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(formatter)
|
||||
root.addHandler(console_handler)
|
||||
|
||||
if not args.debug:
|
||||
# disable scapy and tensorflow logging
|
||||
logging.getLogger("scapy").disabled = True
|
||||
logging.getLogger('tensorflow').disabled = True
|
||||
# https://stackoverflow.com/questions/15777951/how-to-suppress-pandas-future-warning
|
||||
warnings.simplefilter(action='ignore', category=FutureWarning)
|
||||
warnings.simplefilter(action='ignore', category=DeprecationWarning)
|
||||
# https://stackoverflow.com/questions/24344045/how-can-i-completely-remove-any-logging-from-requests-module-in-python?noredirect=1&lq=1
|
||||
logging.getLogger("urllib3").propagate = False
|
||||
requests_log = logging.getLogger("requests")
|
||||
requests_log.addHandler(logging.NullHandler())
|
||||
requests_log.prpagate = False
|
||||
|
||||
|
||||
def log_rotation(filename, cfg):
|
||||
rotation = cfg['rotation']
|
||||
if not rotation['enabled']:
|
||||
return
|
||||
elif not os.path.isfile(filename):
|
||||
return
|
||||
|
||||
stats = os.stat(filename)
|
||||
# specify a maximum size to rotate ( format is 10/10B, 10K, 10M 10G )
|
||||
if rotation['size']:
|
||||
max_size = parse_max_size(rotation['size'])
|
||||
if stats.st_size >= max_size:
|
||||
do_rotate(filename, stats, cfg)
|
||||
else:
|
||||
raise Exception("log rotation is enabled but log.rotation.size was not specified")
|
||||
|
||||
|
||||
def parse_max_size(s):
|
||||
parts = re.findall(r'(^\d+)([bBkKmMgG]?)', s)
|
||||
if len(parts) != 1 or len(parts[0]) != 2:
|
||||
raise Exception("can't parse %s as a max size" % s)
|
||||
|
||||
num, unit = parts[0]
|
||||
num = int(num)
|
||||
unit = unit.lower()
|
||||
|
||||
if unit == 'k':
|
||||
return num * 1024
|
||||
elif unit == 'm':
|
||||
return num * 1024 * 1024
|
||||
elif unit == 'g':
|
||||
return num * 1024 * 1024 * 1024
|
||||
else:
|
||||
return num
|
||||
|
||||
|
||||
def do_rotate(filename, stats, cfg):
|
||||
base_path = os.path.dirname(filename)
|
||||
name = os.path.splitext(os.path.basename(filename))[0]
|
||||
archive_filename = os.path.join(base_path, "%s.gz" % name)
|
||||
counter = 2
|
||||
|
||||
while os.path.exists(archive_filename):
|
||||
archive_filename = os.path.join(base_path, "%s-%d.gz" % (name, counter))
|
||||
counter += 1
|
||||
|
||||
log_filename = archive_filename.replace('gz', 'log')
|
||||
|
||||
print("%s is %d bytes big, rotating to %s ..." % (filename, stats.st_size, log_filename))
|
||||
|
||||
shutil.move(filename, log_filename)
|
||||
|
||||
print("compressing to %s ..." % archive_filename)
|
||||
|
||||
with open(log_filename, 'rb') as src:
|
||||
with gzip.open(archive_filename, 'wb') as dst:
|
||||
dst.writelines(src)
|
||||
|
||||
os.remove(log_filename)
|
||||
|
@ -17,7 +17,7 @@ class AsyncAdvertiser(object):
|
||||
self._keypair = keypair
|
||||
self._advertisement = {
|
||||
'name': pwnagotchi.name(),
|
||||
'version': pwnagotchi.version,
|
||||
'version': pwnagotchi.__version__,
|
||||
'identity': self._keypair.fingerprint,
|
||||
'face': faces.FRIEND,
|
||||
'pwnd_run': 0,
|
||||
|
@ -1,16 +1,17 @@
|
||||
import os
|
||||
import glob
|
||||
import _thread
|
||||
import threading
|
||||
import importlib, importlib.util
|
||||
import logging
|
||||
from pwnagotchi.ui import view
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
|
||||
loaded = {}
|
||||
database = {}
|
||||
locks = {}
|
||||
|
||||
THREAD_POOL_SIZE = 10
|
||||
executor = ThreadPoolExecutor(max_workers=THREAD_POOL_SIZE)
|
||||
|
||||
class Plugin:
|
||||
@classmethod
|
||||
@ -29,41 +30,57 @@ class Plugin:
|
||||
if cb is not None and callable(cb):
|
||||
locks["%s::%s" % (plugin_name, attr_name)] = threading.Lock()
|
||||
|
||||
|
||||
def toggle_plugin(name, enable=True):
|
||||
"""
|
||||
Load or unload a plugin
|
||||
|
||||
returns True if changed, otherwise False
|
||||
"""
|
||||
import pwnagotchi
|
||||
from pwnagotchi.ui import view
|
||||
from pwnagotchi.utils import save_config
|
||||
|
||||
global loaded, database
|
||||
|
||||
if pwnagotchi.config:
|
||||
if not name in pwnagotchi.config['main']['plugins']:
|
||||
pwnagotchi.config['main']['plugins'][name] = dict()
|
||||
pwnagotchi.config['main']['plugins'][name]['enabled'] = enable
|
||||
save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml')
|
||||
|
||||
if not enable and name in loaded:
|
||||
if getattr(loaded[name], 'on_unload', None):
|
||||
loaded[name].on_unload(view.ROOT)
|
||||
del loaded[name]
|
||||
|
||||
return True
|
||||
|
||||
if enable and name in database and name not in loaded:
|
||||
load_from_file(database[name])
|
||||
if name in loaded and pwnagotchi.config and name in pwnagotchi.config['main']['plugins']:
|
||||
loaded[name].options = pwnagotchi.config['main']['plugins'][name]
|
||||
one(name, 'loaded')
|
||||
if pwnagotchi.config:
|
||||
one(name, 'config_changed', pwnagotchi.config)
|
||||
one(name, 'ui_setup', view.ROOT)
|
||||
one(name, 'ready', view.ROOT._agent)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def on(event_name, *args, **kwargs):
|
||||
for plugin_name, plugin in loaded.items():
|
||||
for plugin_name in loaded.keys():
|
||||
one(plugin_name, event_name, *args, **kwargs)
|
||||
|
||||
|
||||
def locked_cb(lock_name, cb, *args, **kwargs):
|
||||
global locks
|
||||
|
||||
if lock_name not in locks:
|
||||
locks[lock_name] = threading.Lock()
|
||||
|
||||
with locks[lock_name]:
|
||||
cb(*args, *kwargs)
|
||||
|
||||
|
||||
def one(plugin_name, event_name, *args, **kwargs):
|
||||
global loaded
|
||||
|
||||
@ -75,12 +92,11 @@ def one(plugin_name, event_name, *args, **kwargs):
|
||||
try:
|
||||
lock_name = "%s::%s" % (plugin_name, cb_name)
|
||||
locked_cb_args = (lock_name, callback, *args, *kwargs)
|
||||
_thread.start_new_thread(locked_cb, locked_cb_args)
|
||||
executor.submit(locked_cb, *locked_cb_args)
|
||||
except Exception as e:
|
||||
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
|
||||
logging.error(e, exc_info=True)
|
||||
|
||||
|
||||
def load_from_file(filename):
|
||||
logging.debug("loading %s" % filename)
|
||||
plugin_name = os.path.basename(filename.replace(".py", ""))
|
||||
@ -89,7 +105,6 @@ def load_from_file(filename):
|
||||
spec.loader.exec_module(instance)
|
||||
return plugin_name, instance
|
||||
|
||||
|
||||
def load_from_path(path, enabled=()):
|
||||
global loaded, database
|
||||
logging.debug("loading plugins from %s - enabled: %s" % (path, enabled))
|
||||
@ -105,7 +120,6 @@ def load_from_path(path, enabled=()):
|
||||
|
||||
return loaded
|
||||
|
||||
|
||||
def load(config):
|
||||
enabled = [name for name, options in config['main']['plugins'].items() if
|
||||
'enabled' in options and options['enabled']]
|
||||
@ -123,3 +137,4 @@ def load(config):
|
||||
plugin.options = config['main']['plugins'][name]
|
||||
|
||||
on('loaded')
|
||||
on('config_changed', config)
|
||||
|
394
pwnagotchi/plugins/cmd.py
Normal file
394
pwnagotchi/plugins/cmd.py
Normal file
@ -0,0 +1,394 @@
|
||||
# Handles the commandline stuff
|
||||
|
||||
import os
|
||||
import logging
|
||||
import glob
|
||||
import re
|
||||
import shutil
|
||||
from fnmatch import fnmatch
|
||||
from pwnagotchi.utils import download_file, unzip, save_config, parse_version, md5
|
||||
from pwnagotchi.plugins import default_path
|
||||
|
||||
|
||||
SAVE_DIR = '/usr/local/share/pwnagotchi/available-plugins/'
|
||||
DEFAULT_INSTALL_PATH = '/usr/local/share/pwnagotchi/installed-plugins/'
|
||||
|
||||
|
||||
def add_parsers(parser):
|
||||
"""
|
||||
Adds the plugins subcommand to a given argparse.ArgumentParser
|
||||
"""
|
||||
subparsers = parser.add_subparsers()
|
||||
## pwnagotchi plugins
|
||||
parser_plugins = subparsers.add_parser('plugins')
|
||||
plugin_subparsers = parser_plugins.add_subparsers(dest='plugincmd')
|
||||
|
||||
## pwnagotchi plugins search
|
||||
parser_plugins_search = plugin_subparsers.add_parser('search', help='Search for pwnagotchi plugins')
|
||||
parser_plugins_search.add_argument('pattern', type=str, help="Search expression (wildcards allowed)")
|
||||
|
||||
## pwnagotchi plugins list
|
||||
parser_plugins_list = plugin_subparsers.add_parser('list', help='List available pwnagotchi plugins')
|
||||
parser_plugins_list.add_argument('-i', '--installed', action='store_true', required=False, help='List also installed plugins')
|
||||
|
||||
## pwnagotchi plugins update
|
||||
parser_plugins_update = plugin_subparsers.add_parser('update', help='Updates the database')
|
||||
|
||||
## pwnagotchi plugins upgrade
|
||||
parser_plugins_upgrade = plugin_subparsers.add_parser('upgrade', help='Upgrades plugins')
|
||||
parser_plugins_upgrade.add_argument('pattern', type=str, nargs='?', default='*', help="Filter expression (wildcards allowed)")
|
||||
|
||||
## pwnagotchi plugins enable
|
||||
parser_plugins_enable = plugin_subparsers.add_parser('enable', help='Enables a plugin')
|
||||
parser_plugins_enable.add_argument('name', type=str, help='Name of the plugin')
|
||||
|
||||
## pwnagotchi plugins disable
|
||||
parser_plugins_disable = plugin_subparsers.add_parser('disable', help='Disables a plugin')
|
||||
parser_plugins_disable.add_argument('name', type=str, help='Name of the plugin')
|
||||
|
||||
## pwnagotchi plugins install
|
||||
parser_plugins_install = plugin_subparsers.add_parser('install', help='Installs a plugin')
|
||||
parser_plugins_install.add_argument('name', type=str, help='Name of the plugin')
|
||||
|
||||
## pwnagotchi plugins uninstall
|
||||
parser_plugins_uninstall = plugin_subparsers.add_parser('uninstall', help='Uninstalls a plugin')
|
||||
parser_plugins_uninstall.add_argument('name', type=str, help='Name of the plugin')
|
||||
|
||||
## pwnagotchi plugins edit
|
||||
parser_plugins_edit = plugin_subparsers.add_parser('edit', help='Edit the options')
|
||||
parser_plugins_edit.add_argument('name', type=str, help='Name of the plugin')
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def used_plugin_cmd(args):
|
||||
"""
|
||||
Checks if the plugins subcommand was used
|
||||
"""
|
||||
return hasattr(args, 'plugincmd')
|
||||
|
||||
|
||||
def handle_cmd(args, config):
|
||||
"""
|
||||
Parses the arguments and does the thing the user wants
|
||||
"""
|
||||
if args.plugincmd == 'update':
|
||||
return update(config)
|
||||
elif args.plugincmd == 'search':
|
||||
args.installed = True # also search in installed plugins
|
||||
return list_plugins(args, config, args.pattern)
|
||||
elif args.plugincmd == 'install':
|
||||
return install(args, config)
|
||||
elif args.plugincmd == 'uninstall':
|
||||
return uninstall(args, config)
|
||||
elif args.plugincmd == 'list':
|
||||
return list_plugins(args, config)
|
||||
elif args.plugincmd == 'enable':
|
||||
return enable(args, config)
|
||||
elif args.plugincmd == 'disable':
|
||||
return disable(args, config)
|
||||
elif args.plugincmd == 'upgrade':
|
||||
return upgrade(args, config, args.pattern)
|
||||
elif args.plugincmd == 'edit':
|
||||
return edit(args, config)
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def edit(args, config):
|
||||
"""
|
||||
Edit the config of the plugin
|
||||
"""
|
||||
plugin = args.name
|
||||
editor = os.environ.get('EDITOR', 'vim') # because vim is the best
|
||||
|
||||
if plugin not in config['main']['plugins']:
|
||||
return 1
|
||||
|
||||
plugin_config = {'main': {'plugins': {plugin: config['main']['plugins'][plugin]}}}
|
||||
|
||||
import toml
|
||||
from subprocess import call
|
||||
from tempfile import NamedTemporaryFile
|
||||
from pwnagotchi.utils import DottedTomlEncoder
|
||||
|
||||
new_plugin_config = None
|
||||
with NamedTemporaryFile(suffix=".tmp", mode='r+t') as tmp:
|
||||
tmp.write(toml.dumps(plugin_config, encoder=DottedTomlEncoder()))
|
||||
tmp.flush()
|
||||
rc = call([editor, tmp.name])
|
||||
if rc != 0:
|
||||
return rc
|
||||
tmp.seek(0)
|
||||
new_plugin_config = toml.load(tmp)
|
||||
|
||||
config['main']['plugins'][plugin] = new_plugin_config['main']['plugins'][plugin]
|
||||
save_config(config, args.user_config)
|
||||
return 0
|
||||
|
||||
|
||||
def enable(args, config):
|
||||
"""
|
||||
Enables the given plugin and saves the config to disk
|
||||
"""
|
||||
if args.name not in config['main']['plugins']:
|
||||
config['main']['plugins'][args.name] = dict()
|
||||
config['main']['plugins'][args.name]['enabled'] = True
|
||||
save_config(config, args.user_config)
|
||||
return 0
|
||||
|
||||
|
||||
def disable(args, config):
|
||||
"""
|
||||
Disables the given plugin and saves the config to disk
|
||||
"""
|
||||
if args.name not in config['main']['plugins']:
|
||||
config['main']['plugins'][args.name] = dict()
|
||||
config['main']['plugins'][args.name]['enabled'] = False
|
||||
save_config(config, args.user_config)
|
||||
return 0
|
||||
|
||||
|
||||
def upgrade(args, config, pattern='*'):
|
||||
"""
|
||||
Upgrades the given plugin
|
||||
"""
|
||||
available = _get_available()
|
||||
installed = _get_installed(config)
|
||||
|
||||
for plugin, filename in installed.items():
|
||||
if not fnmatch(plugin, pattern) or plugin not in available:
|
||||
continue
|
||||
|
||||
available_version = _extract_version(available[plugin])
|
||||
installed_version = _extract_version(filename)
|
||||
|
||||
if installed_version and available_version:
|
||||
if available_version <= installed_version:
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
|
||||
logging.info('Upgrade %s from %s to %s', plugin, '.'.join(installed_version), '.'.join(available_version))
|
||||
shutil.copyfile(available[plugin], installed[plugin])
|
||||
|
||||
# maybe has config
|
||||
for conf in glob.glob(available[plugin].replace('.py', '.y?ml')):
|
||||
dst = os.path.join(os.path.dirname(installed[plugin]), os.path.basename(conf))
|
||||
if os.path.exists(dst) and md5(dst) != md5(conf):
|
||||
# backup
|
||||
logging.info('Backing up config: %s', os.path.basename(conf))
|
||||
shutil.move(dst, dst + '.bak')
|
||||
shutil.copyfile(conf, dst)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def list_plugins(args, config, pattern='*'):
|
||||
"""
|
||||
Lists the available and installed plugins
|
||||
"""
|
||||
found = False
|
||||
|
||||
line = "|{name:^{width}}|{version:^9}|{enabled:^10}|{status:^15}|"
|
||||
|
||||
available = _get_available()
|
||||
installed = _get_installed(config)
|
||||
|
||||
available_and_installed = set(list(available.keys()) + list(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 = max(map(len, max_len_list))
|
||||
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
|
||||
|
||||
print('-' * line_length)
|
||||
print(header)
|
||||
print('-' * line_length)
|
||||
|
||||
if args.installed:
|
||||
# only installed (maybe update available?)
|
||||
for plugin, filename in sorted(installed.items()):
|
||||
if not fnmatch(plugin, pattern):
|
||||
continue
|
||||
found = True
|
||||
installed_version = _extract_version(filename)
|
||||
available_version = None
|
||||
if plugin in available:
|
||||
available_version = _extract_version(available[plugin])
|
||||
|
||||
status = "installed"
|
||||
if installed_version and available_version:
|
||||
if available_version > installed_version:
|
||||
status = "installed (^)"
|
||||
|
||||
enabled = 'enabled' if plugin in config['main']['plugins'] and \
|
||||
'enabled' in config['main']['plugins'][plugin] and \
|
||||
config['main']['plugins'][plugin]['enabled'] \
|
||||
else 'disabled'
|
||||
|
||||
print(line.format(name=plugin, width=max_len, version='.'.join(installed_version), enabled=enabled, status=status))
|
||||
|
||||
|
||||
for plugin in sorted(available_not_installed):
|
||||
if not fnmatch(plugin, pattern):
|
||||
continue
|
||||
found = True
|
||||
available_version = _extract_version(available[plugin])
|
||||
print(line.format(name=plugin, width=max_len, version='.'.join(available_version), enabled='-', status='available'))
|
||||
|
||||
print('-' * line_length)
|
||||
|
||||
if not found:
|
||||
logging.info('Maybe try: pwnagotchi plugins update')
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def _extract_version(filename):
|
||||
"""
|
||||
Extracts the version from a python file
|
||||
"""
|
||||
plugin_content = open(filename, 'rt').read()
|
||||
m = re.search(r'__version__[\t ]*=[\t ]*[\'\"]([^\"\']+)', plugin_content)
|
||||
if m:
|
||||
return parse_version(m.groups()[0])
|
||||
return None
|
||||
|
||||
|
||||
def _get_available():
|
||||
"""
|
||||
Get all availaible plugins
|
||||
"""
|
||||
available = dict()
|
||||
for filename in glob.glob(os.path.join(SAVE_DIR, "*.py")):
|
||||
plugin_name = os.path.basename(filename.replace(".py", ""))
|
||||
available[plugin_name] = filename
|
||||
return available
|
||||
|
||||
|
||||
def _get_installed(config):
|
||||
"""
|
||||
Get all installed plugins
|
||||
"""
|
||||
installed = dict()
|
||||
search_dirs = [ default_path, config['main']['custom_plugins'] ]
|
||||
for search_dir in search_dirs:
|
||||
if search_dir:
|
||||
for filename in glob.glob(os.path.join(search_dir, "*.py")):
|
||||
plugin_name = os.path.basename(filename.replace(".py", ""))
|
||||
installed[plugin_name] = filename
|
||||
return installed
|
||||
|
||||
|
||||
def uninstall(args, config):
|
||||
"""
|
||||
Uninstalls a plugin
|
||||
"""
|
||||
plugin_name = args.name
|
||||
installed = _get_installed(config)
|
||||
if plugin_name not in installed:
|
||||
logging.error('Plugin %s is not installed.', plugin_name)
|
||||
return 1
|
||||
os.remove(installed[plugin_name])
|
||||
return 0
|
||||
|
||||
|
||||
def install(args, config):
|
||||
"""
|
||||
Installs the given plugin
|
||||
"""
|
||||
global DEFAULT_INSTALL_PATH
|
||||
plugin_name = args.name
|
||||
available = _get_available()
|
||||
installed = _get_installed(config)
|
||||
|
||||
if plugin_name not in available:
|
||||
logging.error('%s not found.', plugin_name)
|
||||
return 1
|
||||
|
||||
if plugin_name in installed:
|
||||
logging.error('%s already installed.', plugin_name)
|
||||
|
||||
# install into custom_plugins path
|
||||
install_path = config['main']['custom_plugins']
|
||||
if not install_path:
|
||||
install_path = DEFAULT_INSTALL_PATH
|
||||
config['main']['custom_plugins'] = install_path
|
||||
save_config(config, args.user_config)
|
||||
|
||||
os.makedirs(install_path, exist_ok=True)
|
||||
|
||||
shutil.copyfile(available[plugin_name], os.path.join(install_path, os.path.basename(available[plugin_name])))
|
||||
|
||||
# maybe has config
|
||||
for conf in glob.glob(available[plugin_name].replace('.py', '.y?ml')):
|
||||
dst = os.path.join(install_path, os.path.basename(conf))
|
||||
if os.path.exists(dst) and md5(dst) != md5(conf):
|
||||
# backup
|
||||
logging.info('Backing up config: %s', os.path.basename(conf))
|
||||
shutil.move(dst, dst + '.bak')
|
||||
shutil.copyfile(conf, dst)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _analyse_dir(path):
|
||||
results = dict()
|
||||
path += '*' if path.endswith('/') else '/*'
|
||||
for filename in glob.glob(path, recursive=True):
|
||||
if not os.path.isfile(filename):
|
||||
continue
|
||||
try:
|
||||
results[filename] = md5(filename)
|
||||
except OSError:
|
||||
continue
|
||||
return results
|
||||
|
||||
|
||||
def update(config):
|
||||
"""
|
||||
Updates the database
|
||||
"""
|
||||
global SAVE_DIR
|
||||
|
||||
urls = config['main']['custom_plugin_repos']
|
||||
if not urls:
|
||||
logging.info('No plugin repositories configured.')
|
||||
return 1
|
||||
|
||||
rc = 0
|
||||
for idx, REPO_URL in enumerate(urls):
|
||||
DEST = os.path.join(SAVE_DIR, 'plugins%d.zip' % idx)
|
||||
logging.info('Downloading plugins from %s to %s', REPO_URL, DEST)
|
||||
|
||||
try:
|
||||
os.makedirs(SAVE_DIR, exist_ok=True)
|
||||
before_update = _analyse_dir(SAVE_DIR)
|
||||
|
||||
download_file(REPO_URL, os.path.join(SAVE_DIR, DEST))
|
||||
|
||||
logging.info('Unzipping...')
|
||||
unzip(DEST, SAVE_DIR, strip_dirs=1)
|
||||
|
||||
after_update = _analyse_dir(SAVE_DIR)
|
||||
|
||||
b_len = len(before_update)
|
||||
a_len = len(after_update)
|
||||
|
||||
if a_len > b_len:
|
||||
logging.info('Found %d new file(s).', a_len - b_len)
|
||||
|
||||
changed = 0
|
||||
for filename, filehash in after_update.items():
|
||||
if filename in before_update and filehash != before_update[filename]:
|
||||
changed += 1
|
||||
|
||||
if changed:
|
||||
logging.info('%d file(s) were changed.', changed)
|
||||
|
||||
except Exception as ex:
|
||||
logging.error('Error while updating plugins: %s', ex)
|
||||
rc = 1
|
||||
return rc
|
@ -6,16 +6,16 @@ import requests
|
||||
import platform
|
||||
import shutil
|
||||
import glob
|
||||
import pkg_resources
|
||||
from threading import Lock
|
||||
import time
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.utils import StatusFile
|
||||
from pwnagotchi.utils import StatusFile, parse_version as version_to_tuple
|
||||
|
||||
|
||||
def check(version, repo, native=True):
|
||||
logging.debug("checking remote version for %s, local is %s" % (repo, version))
|
||||
def check_remote_version(version, repo, native=True):
|
||||
logging.debug("Checking remote version for %s, local is %s" % (repo, version))
|
||||
info = {
|
||||
'repo': repo,
|
||||
'current': version,
|
||||
@ -25,81 +25,92 @@ def check(version, repo, native=True):
|
||||
'arch': platform.machine()
|
||||
}
|
||||
|
||||
resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo)
|
||||
latest = resp.json()
|
||||
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
||||
is_arm = info['arch'].startswith('arm')
|
||||
try:
|
||||
resp = requests.get(f"https://api.github.com/repos/{repo}/releases/latest")
|
||||
resp.raise_for_status()
|
||||
latest = resp.json()
|
||||
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
||||
|
||||
local = pkg_resources.parse_version(info['current'])
|
||||
remote = pkg_resources.parse_version(latest_ver)
|
||||
if remote > local:
|
||||
if not native:
|
||||
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
|
||||
else:
|
||||
# check if this release is compatible with arm6
|
||||
for asset in latest['assets']:
|
||||
download_url = asset['browser_download_url']
|
||||
if download_url.endswith('.zip') and (
|
||||
info['arch'] in download_url or (is_arm and 'armhf' in download_url)):
|
||||
info['url'] = download_url
|
||||
break
|
||||
is_arm = info['arch'].startswith('arm')
|
||||
local = version_to_tuple(info['current'])
|
||||
remote = version_to_tuple(latest_ver)
|
||||
|
||||
if remote > local:
|
||||
if not native:
|
||||
info['url'] = f"https://github.com/{repo}/archive/{latest['tag_name']}.zip"
|
||||
else:
|
||||
for asset in latest['assets']:
|
||||
download_url = asset['browser_download_url']
|
||||
if download_url.endswith('.zip') and (
|
||||
info['arch'] in download_url or (is_arm and 'armhf' in download_url)):
|
||||
info['url'] = download_url
|
||||
break
|
||||
except Exception as e:
|
||||
logging.error(f"Error checking remote version for {repo}: {e}")
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def make_path_for(name):
|
||||
path = os.path.join("/tmp/updates/", name)
|
||||
if os.path.exists(path):
|
||||
logging.debug("[update] deleting %s" % path)
|
||||
shutil.rmtree(path, ignore_errors=True, onerror=None)
|
||||
os.makedirs(path)
|
||||
try:
|
||||
if os.path.exists(path):
|
||||
logging.debug("[update] Deleting %s" % path)
|
||||
shutil.rmtree(path, ignore_errors=True, onerror=None)
|
||||
os.makedirs(path)
|
||||
except Exception as e:
|
||||
logging.error(f"Error creating path for {name}: {e}")
|
||||
return path
|
||||
|
||||
|
||||
def download_and_unzip(name, path, display, update):
|
||||
target = "%s_%s.zip" % (name, update['available'])
|
||||
target = f"{name}_{update['available']}.zip"
|
||||
target_path = os.path.join(path, target)
|
||||
|
||||
logging.info("[update] downloading %s to %s ..." % (update['url'], target_path))
|
||||
display.update(force=True, new_data={'status': 'Downloading %s %s ...' % (name, update['available'])})
|
||||
try:
|
||||
logging.info("[update] Downloading %s to %s ..." % (update['url'], target_path))
|
||||
display.update(force=True, new_data={'status': f'Downloading {name} {update["available"]} ...'})
|
||||
subprocess.run(['wget', '-q', update['url'], '-O', target_path], check=True)
|
||||
|
||||
os.system('wget -q "%s" -O "%s"' % (update['url'], target_path))
|
||||
logging.info("[update] Extracting %s to %s ..." % (target_path, path))
|
||||
display.update(force=True, new_data={'status': f'Extracting {name} {update["available"]} ...'})
|
||||
subprocess.run(['unzip', target_path, '-d', path], check=True)
|
||||
|
||||
logging.info("[update] extracting %s to %s ..." % (target_path, path))
|
||||
display.update(force=True, new_data={'status': 'Extracting %s %s ...' % (name, update['available'])})
|
||||
|
||||
os.system('unzip "%s" -d "%s"' % (target_path, path))
|
||||
except Exception as e:
|
||||
logging.error(f"Error downloading and unzipping {name} update: {e}")
|
||||
|
||||
|
||||
def verify(name, path, source_path, display, update):
|
||||
display.update(force=True, new_data={'status': 'Verifying %s %s ...' % (name, update['available'])})
|
||||
display.update(force=True, new_data={'status': f'Verifying {name} {update["available"]} ...'})
|
||||
|
||||
checksums = glob.glob("%s/*.sha256" % path)
|
||||
if len(checksums) == 0:
|
||||
if update['native']:
|
||||
logging.warning("[update] native update without SHA256 checksum file")
|
||||
return False
|
||||
try:
|
||||
checksums = glob.glob(f"{path}/*.sha256")
|
||||
if len(checksums) == 0:
|
||||
if update['native']:
|
||||
logging.warning("[update] Native update without SHA256 checksum file")
|
||||
return False
|
||||
else:
|
||||
checksum = checksums[0]
|
||||
logging.info(f"[update] Verifying {checksum} for {source_path} ...")
|
||||
|
||||
else:
|
||||
checksum = checksums[0]
|
||||
with open(checksum, 'rt') as fp:
|
||||
expected = fp.read().split('=')[1].strip().lower()
|
||||
|
||||
logging.info("[update] verifying %s for %s ..." % (checksum, source_path))
|
||||
real = subprocess.getoutput(f'sha256sum "{source_path}"').split(' ')[0].strip().lower()
|
||||
|
||||
with open(checksum, 'rt') as fp:
|
||||
expected = fp.read().split('=')[1].strip().lower()
|
||||
if real != expected:
|
||||
logging.warning(f"[update] Checksum mismatch for {source_path}: expected={expected} got={real}")
|
||||
return False
|
||||
|
||||
real = subprocess.getoutput('sha256sum "%s"' % source_path).split(' ')[0].strip().lower()
|
||||
|
||||
if real != expected:
|
||||
logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (source_path, expected, real))
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.error(f"Error verifying {name} update: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def install(display, update):
|
||||
name = update['repo'].split('/')[1]
|
||||
|
||||
path = make_path_for(name)
|
||||
|
||||
download_and_unzip(name, path, display, update)
|
||||
@ -108,37 +119,70 @@ def install(display, update):
|
||||
if not verify(name, path, source_path, display, update):
|
||||
return False
|
||||
|
||||
logging.info("[update] installing %s ..." % name)
|
||||
display.update(force=True, new_data={'status': 'Installing %s %s ...' % (name, update['available'])})
|
||||
try:
|
||||
logging.info("[update] Installing %s ..." % name)
|
||||
display.update(force=True, new_data={'status': f'Installing {name} {update["available"]} ...'})
|
||||
|
||||
if update['native']:
|
||||
dest_path = subprocess.getoutput("which %s" % name)
|
||||
if dest_path == "":
|
||||
logging.warning("[update] can't find path for %s" % name)
|
||||
return False
|
||||
if update['native']:
|
||||
dest_path = subprocess.getoutput(f"which {name}")
|
||||
if dest_path == "":
|
||||
logging.warning(f"[update] Can't find path for {name}")
|
||||
return False
|
||||
|
||||
logging.info("[update] stopping %s ..." % update['service'])
|
||||
os.system("service %s stop" % update['service'])
|
||||
os.system("mv %s %s" % (source_path, dest_path))
|
||||
logging.info("[update] restarting %s ..." % update['service'])
|
||||
os.system("service %s start" % update['service'])
|
||||
else:
|
||||
if not os.path.exists(source_path):
|
||||
source_path = "%s-%s" % (source_path, update['available'])
|
||||
logging.info(f"[update] Stopping {update['service']} ...")
|
||||
subprocess.run(["service", update['service'], "stop"], check=True)
|
||||
|
||||
# setup.py is going to install data files for us
|
||||
os.system("cd %s && pip3 install ." % source_path)
|
||||
subprocess.run(["mv", source_path, dest_path], check=True)
|
||||
logging.info(f"[update] Restarting {update['service']} ...")
|
||||
subprocess.run(["service", update['service'], "start"], check=True)
|
||||
else:
|
||||
if not os.path.exists(source_path):
|
||||
source_path = f"{source_path}-{update['available']}"
|
||||
|
||||
subprocess.run(["cd", source_path, "&&", "pip3", "install", "."], check=True, shell=True)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error installing {name} update: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def parse_version(cmd):
|
||||
out = subprocess.getoutput(cmd)
|
||||
for part in out.split(' '):
|
||||
part = part.replace('v', '').strip()
|
||||
if re.search(r'^\d+\.\d+\.\d+.*$', part):
|
||||
return part
|
||||
raise Exception('could not parse version from "%s": output=\n%s' % (cmd, out))
|
||||
try:
|
||||
out = subprocess.getoutput(cmd)
|
||||
for part in out.split(' '):
|
||||
part = part.replace('v', '').strip()
|
||||
if re.search(r'^\d+\.\d+\.\d+.*$', part):
|
||||
return part
|
||||
except Exception as e:
|
||||
logging.error(f"Error parsing version from '{cmd}': {e}")
|
||||
raise Exception(f'Could not parse version from "{cmd}": output=\n{out}')
|
||||
|
||||
|
||||
def check_remote_version_with_retry(version, repo, native=True, max_retries=3):
|
||||
retries = 0
|
||||
while retries < max_retries:
|
||||
try:
|
||||
resp = requests.get(f"https://api.github.com/repos/{repo}/releases/latest")
|
||||
resp.raise_for_status()
|
||||
latest = resp.json()
|
||||
return check_remote_version(version, repo, native)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 403:
|
||||
wait_time = 2 ** retries
|
||||
print(f"Rate limit exceeded. Retrying after {wait_time} seconds...")
|
||||
time.sleep(wait_time)
|
||||
retries += 1
|
||||
else:
|
||||
print(f"Error checking remote version for {repo}: {e}")
|
||||
raise e
|
||||
except requests.exceptions.ConnectionError as ce:
|
||||
wait_time = 2 ** retries
|
||||
print(f"Connection error. Retrying after {wait_time} seconds...")
|
||||
time.sleep(wait_time)
|
||||
retries += 1
|
||||
raise Exception(f"Failed to check remote version for {repo} after {max_retries} retries.")
|
||||
|
||||
|
||||
class AutoUpdate(plugins.Plugin):
|
||||
@ -154,24 +198,27 @@ class AutoUpdate(plugins.Plugin):
|
||||
self.lock = Lock()
|
||||
|
||||
def on_loaded(self):
|
||||
if 'interval' not in self.options or ('interval' in self.options and self.options['interval'] is None):
|
||||
if 'interval' not in self.options or ('interval' in self.options and not self.options['interval']):
|
||||
logging.error("[update] main.plugins.auto-update.interval is not set")
|
||||
return
|
||||
self.ready = True
|
||||
logging.info("[update] plugin loaded.")
|
||||
logging.info("[update] Plugin loaded.")
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
if self.lock.locked():
|
||||
return
|
||||
|
||||
with self.lock:
|
||||
logging.debug("[update] internet connectivity is available (ready %s)" % self.ready)
|
||||
logging.debug("[update] Internet connectivity is available (ready %s)" % self.ready)
|
||||
|
||||
if not self.ready:
|
||||
return
|
||||
|
||||
if self.status.newer_then_hours(self.options['interval']):
|
||||
logging.debug("[update] last check happened less than %d hours ago" % self.options['interval'])
|
||||
logging.debug("[update] Last check happened less than %d hours ago" % self.options['interval'])
|
||||
return
|
||||
|
||||
logging.info("[update] checking for updates ...")
|
||||
logging.info("[update] Checking for updates ...")
|
||||
|
||||
display = agent.view()
|
||||
prev_status = display.get('status')
|
||||
@ -183,15 +230,14 @@ class AutoUpdate(plugins.Plugin):
|
||||
to_check = [
|
||||
('bettercap/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
|
||||
('evilsocket/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
|
||||
('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi')
|
||||
('scifijunk/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
|
||||
]
|
||||
|
||||
for repo, local_version, is_native, svc_name in to_check:
|
||||
info = check(local_version, repo, is_native)
|
||||
info = check_remote_version_with_retry(local_version, repo, is_native)
|
||||
if info['url'] is not None:
|
||||
logging.warning(
|
||||
"update for %s available (local version is '%s'): %s" % (
|
||||
repo, info['current'], info['url']))
|
||||
f"Update for {repo} available (local version is '{info['current']}'): {info['url']}")
|
||||
info['service'] = svc_name
|
||||
to_install.append(info)
|
||||
|
||||
@ -205,9 +251,9 @@ class AutoUpdate(plugins.Plugin):
|
||||
if install(display, update):
|
||||
num_installed += 1
|
||||
else:
|
||||
prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '')
|
||||
prev_status = f"{num_updates} new update{'s' if num_updates > 1 else ''} available!"
|
||||
|
||||
logging.info("[update] done")
|
||||
logging.info("[update] Done")
|
||||
|
||||
self.status.update()
|
||||
|
||||
|
@ -419,15 +419,19 @@ class Device:
|
||||
|
||||
class BTTether(plugins.Plugin):
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '1.0.0'
|
||||
__version__ = '1.1.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This makes the display reachable over bluetooth'
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.ready = False
|
||||
self.options = dict()
|
||||
self.devices = dict()
|
||||
self.lock = Lock()
|
||||
self.running = True
|
||||
self.status = '-'
|
||||
|
||||
|
||||
def on_loaded(self):
|
||||
# new config
|
||||
@ -437,7 +441,7 @@ class BTTether(plugins.Plugin):
|
||||
for device_opt in ['enabled', 'priority', 'scantime', 'search_order',
|
||||
'max_tries', 'share_internet', 'mac', 'ip',
|
||||
'netmask', 'interval']:
|
||||
if device_opt not in options or (device_opt in options and options[device_opt] is None):
|
||||
if device_opt not in options or options[device_opt] is None:
|
||||
logging.error("BT-TETHER: Please specify the %s for device %s.",
|
||||
device_opt, device)
|
||||
break
|
||||
@ -448,8 +452,8 @@ class BTTether(plugins.Plugin):
|
||||
# legacy
|
||||
if 'mac' in self.options:
|
||||
for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
|
||||
if opt not in self.options or (opt in self.options and self.options[opt] is None):
|
||||
logging.error("BT-TETHER: Please specify the %s in your config.yml.", opt)
|
||||
if opt not in self.options or self.options[opt] is None:
|
||||
logging.error("BT-TETHER: Please specify the %s in your config.toml.", opt)
|
||||
return
|
||||
|
||||
self.devices['legacy'] = Device(name='legacy', **self.options)
|
||||
@ -466,22 +470,10 @@ class BTTether(plugins.Plugin):
|
||||
return
|
||||
|
||||
logging.info("BT-TETHER: Successfully loaded ...")
|
||||
self.ready = True
|
||||
|
||||
def on_unload(self, ui):
|
||||
with ui._lock:
|
||||
ui.remove_element('bluetooth')
|
||||
while self.running:
|
||||
time.sleep(1)
|
||||
|
||||
def on_ui_setup(self, ui):
|
||||
with ui._lock:
|
||||
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0),
|
||||
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||
|
||||
def on_ui_update(self, ui):
|
||||
if not self.ready:
|
||||
return
|
||||
|
||||
with self.lock:
|
||||
devices_to_try = list()
|
||||
connected_priorities = list()
|
||||
any_device_connected = False # if this is true, last status on screen should be C
|
||||
@ -508,11 +500,11 @@ class BTTether(plugins.Plugin):
|
||||
dev_remote = bt.wait_for_device(timeout=device.scantime)
|
||||
if dev_remote is None:
|
||||
logging.debug('BT-TETHER: Could not find %s, try again in %d minutes.', device.name, device.interval)
|
||||
ui.set('bluetooth', 'NF')
|
||||
self.status = 'NF'
|
||||
continue
|
||||
except Exception as bt_ex:
|
||||
logging.error(bt_ex)
|
||||
ui.set('bluetooth', 'NF')
|
||||
self.status = 'NF'
|
||||
continue
|
||||
|
||||
paired = bt.is_paired()
|
||||
@ -521,7 +513,7 @@ class BTTether(plugins.Plugin):
|
||||
logging.debug('BT-TETHER: Paired with %s.', device.name)
|
||||
else:
|
||||
logging.debug('BT-TETHER: Pairing with %s failed ...', device.name)
|
||||
ui.set('bluetooth', 'PE')
|
||||
self.status = 'PE'
|
||||
continue
|
||||
else:
|
||||
logging.debug('BT-TETHER: Already paired.')
|
||||
@ -539,17 +531,17 @@ class BTTether(plugins.Plugin):
|
||||
continue
|
||||
|
||||
if interface is None:
|
||||
ui.set('bluetooth', 'BE')
|
||||
self.status = 'BE'
|
||||
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
|
||||
continue
|
||||
|
||||
logging.debug('BT-TETHER: Created interface (%s)', interface)
|
||||
ui.set('bluetooth', 'C')
|
||||
self.status = 'C'
|
||||
any_device_connected = True
|
||||
device.tries = 0 # reset tries
|
||||
else:
|
||||
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
|
||||
ui.set('bluetooth', 'NF')
|
||||
self.status = 'NF'
|
||||
continue
|
||||
|
||||
addr = f"{device.ip}/{device.netmask}"
|
||||
@ -561,7 +553,7 @@ class BTTether(plugins.Plugin):
|
||||
wrapped_interface = IfaceWrapper(interface)
|
||||
logging.debug('BT-TETHER: Add ip to %s', interface)
|
||||
if not wrapped_interface.set_addr(addr):
|
||||
ui.set('bluetooth', 'AE')
|
||||
self.status = 'AE'
|
||||
logging.debug("BT-TETHER: Could not add ip to %s", interface)
|
||||
continue
|
||||
|
||||
@ -580,4 +572,20 @@ class BTTether(plugins.Plugin):
|
||||
resolv.write(nameserver + 'nameserver 9.9.9.9\n')
|
||||
|
||||
if any_device_connected:
|
||||
ui.set('bluetooth', 'C')
|
||||
self.status = 'C'
|
||||
|
||||
|
||||
def on_unload(self, ui):
|
||||
self.running = False
|
||||
with ui._lock:
|
||||
ui.remove_element('bluetooth')
|
||||
|
||||
|
||||
def on_ui_setup(self, ui):
|
||||
with ui._lock:
|
||||
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0),
|
||||
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||
|
||||
|
||||
def on_ui_update(self, ui):
|
||||
ui.set('bluetooth', self.status)
|
||||
|
@ -10,10 +10,13 @@ from pwnagotchi.ui.view import BLACK
|
||||
|
||||
class GPS(plugins.Plugin):
|
||||
__author__ = "evilsocket@gmail.com"
|
||||
__version__ = "1.0.0"
|
||||
__version__ = "1.0.1"
|
||||
__license__ = "GPL3"
|
||||
__description__ = "Save GPS coordinates whenever an handshake is captured."
|
||||
|
||||
LINE_SPACING = 10
|
||||
LABEL_SPACING = 0
|
||||
|
||||
def __init__(self):
|
||||
self.running = False
|
||||
self.coordinates = None
|
||||
@ -22,18 +25,20 @@ class GPS(plugins.Plugin):
|
||||
logging.info(f"gps plugin loaded for {self.options['device']}")
|
||||
|
||||
def on_ready(self, agent):
|
||||
if os.path.exists(self.options["device"]):
|
||||
if os.path.exists(self.options["device"]) or ":" in self.options["device"]:
|
||||
logging.info(
|
||||
f"enabling bettercap's gps module for {self.options['device']}"
|
||||
)
|
||||
try:
|
||||
agent.run("gps off")
|
||||
except Exception:
|
||||
logging.info(f"bettercap gps module was already off")
|
||||
pass
|
||||
|
||||
agent.run(f"set gps.device {self.options['device']}")
|
||||
agent.run(f"set gps.baudrate {self.options['speed']}")
|
||||
agent.run("gps on")
|
||||
logging.info(f"bettercap gps module enabled on {self.options['device']}")
|
||||
self.running = True
|
||||
else:
|
||||
logging.warning("no GPS detected")
|
||||
@ -44,37 +49,63 @@ class GPS(plugins.Plugin):
|
||||
self.coordinates = info["gps"]
|
||||
gps_filename = filename.replace(".pcap", ".gps.json")
|
||||
|
||||
logging.info(f"saving GPS to {gps_filename} ({self.coordinates})")
|
||||
with open(gps_filename, "w+t") as fp:
|
||||
json.dump(self.coordinates, fp)
|
||||
if self.coordinates and all([
|
||||
# avoid 0.000... measurements
|
||||
self.coordinates["Latitude"], self.coordinates["Longitude"]
|
||||
]):
|
||||
logging.info(f"saving GPS to {gps_filename} ({self.coordinates})")
|
||||
with open(gps_filename, "w+t") as fp:
|
||||
json.dump(self.coordinates, fp)
|
||||
else:
|
||||
logging.info("not saving GPS. Couldn't find location.")
|
||||
|
||||
def on_ui_setup(self, ui):
|
||||
# add coordinates for other displays
|
||||
if ui.is_waveshare_v2():
|
||||
lat_pos = (127, 75)
|
||||
lon_pos = (122, 84)
|
||||
alt_pos = (127, 94)
|
||||
elif ui.is_waveshare_v1():
|
||||
lat_pos = (130, 70)
|
||||
lon_pos = (125, 80)
|
||||
alt_pos = (130, 90)
|
||||
elif ui.is_inky():
|
||||
# guessed values, add tested ones if you can
|
||||
lat_pos = (112, 30)
|
||||
lon_pos = (112, 49)
|
||||
alt_pos = (87, 63)
|
||||
elif ui.is_waveshare144lcd():
|
||||
# guessed values, add tested ones if you can
|
||||
lat_pos = (67, 73)
|
||||
lon_pos = (62, 83)
|
||||
alt_pos = (67, 93)
|
||||
else:
|
||||
# guessed values, add tested ones if you can
|
||||
lat_pos = (127, 51)
|
||||
lon_pos = (127, 56)
|
||||
alt_pos = (102, 71)
|
||||
try:
|
||||
# Configure line_spacing
|
||||
line_spacing = int(self.options['linespacing'])
|
||||
except Exception:
|
||||
# Set default value
|
||||
line_spacing = self.LINE_SPACING
|
||||
|
||||
label_spacing = 0
|
||||
try:
|
||||
# Configure position
|
||||
pos = self.options['position'].split(',')
|
||||
pos = [int(x.strip()) for x in pos]
|
||||
lat_pos = (pos[0] + 5, pos[1])
|
||||
lon_pos = (pos[0], pos[1] + line_spacing)
|
||||
alt_pos = (pos[0] + 5, pos[1] + (2 * line_spacing))
|
||||
except Exception:
|
||||
# Set default value based on display type
|
||||
if ui.is_waveshare_v2():
|
||||
lat_pos = (127, 74)
|
||||
lon_pos = (122, 84)
|
||||
alt_pos = (127, 94)
|
||||
elif ui.is_waveshare_v1():
|
||||
lat_pos = (130, 70)
|
||||
lon_pos = (125, 80)
|
||||
alt_pos = (130, 90)
|
||||
elif ui.is_inky():
|
||||
lat_pos = (127, 60)
|
||||
lon_pos = (122, 70)
|
||||
alt_pos = (127, 80)
|
||||
elif ui.is_waveshare144lcd():
|
||||
# guessed values, add tested ones if you can
|
||||
lat_pos = (67, 73)
|
||||
lon_pos = (62, 83)
|
||||
alt_pos = (67, 93)
|
||||
elif ui.is_dfrobot_v2():
|
||||
lat_pos = (127, 74)
|
||||
lon_pos = (122, 84)
|
||||
alt_pos = (127, 94)
|
||||
elif ui.is_waveshare27inch():
|
||||
lat_pos = (6, 120)
|
||||
lon_pos = (1, 135)
|
||||
alt_pos = (6, 150)
|
||||
else:
|
||||
# guessed values, add tested ones if you can
|
||||
lat_pos = (127, 51)
|
||||
lon_pos = (122, 61)
|
||||
alt_pos = (127, 71)
|
||||
|
||||
ui.add_element(
|
||||
"latitude",
|
||||
@ -85,7 +116,7 @@ class GPS(plugins.Plugin):
|
||||
position=lat_pos,
|
||||
label_font=fonts.Small,
|
||||
text_font=fonts.Small,
|
||||
label_spacing=label_spacing,
|
||||
label_spacing=self.LABEL_SPACING,
|
||||
),
|
||||
)
|
||||
ui.add_element(
|
||||
@ -97,7 +128,7 @@ class GPS(plugins.Plugin):
|
||||
position=lon_pos,
|
||||
label_font=fonts.Small,
|
||||
text_font=fonts.Small,
|
||||
label_spacing=label_spacing,
|
||||
label_spacing=self.LABEL_SPACING,
|
||||
),
|
||||
)
|
||||
ui.add_element(
|
||||
@ -109,11 +140,10 @@ class GPS(plugins.Plugin):
|
||||
position=alt_pos,
|
||||
label_font=fonts.Small,
|
||||
text_font=fonts.Small,
|
||||
label_spacing=label_spacing,
|
||||
label_spacing=self.LABEL_SPACING,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def on_unload(self, ui):
|
||||
with ui._lock:
|
||||
ui.remove_element('latitude')
|
||||
@ -128,5 +158,5 @@ class GPS(plugins.Plugin):
|
||||
# last char is sometimes not completely drawn ¯\_(ツ)_/¯
|
||||
# using an ending-whitespace as workaround on each line
|
||||
ui.set("latitude", f"{self.coordinates['Latitude']:.4f} ")
|
||||
ui.set("longitude", f" {self.coordinates['Longitude']:.4f} ")
|
||||
ui.set("altitude", f" {self.coordinates['Altitude']:.1f}m ")
|
||||
ui.set("longitude", f"{self.coordinates['Longitude']:.4f} ")
|
||||
ui.set("altitude", f"{self.coordinates['Altitude']:.1f}m ")
|
||||
|
@ -7,6 +7,7 @@ import re
|
||||
import pwnagotchi.grid as grid
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
|
||||
from threading import Lock
|
||||
|
||||
|
||||
def parse_pcap(filename):
|
||||
@ -54,6 +55,7 @@ class Grid(plugins.Plugin):
|
||||
|
||||
self.unread_messages = 0
|
||||
self.total_messages = 0
|
||||
self.lock = Lock()
|
||||
|
||||
def is_excluded(self, what):
|
||||
for skip in self.options['exclude']:
|
||||
@ -122,21 +124,25 @@ class Grid(plugins.Plugin):
|
||||
def on_internet_available(self, agent):
|
||||
logging.debug("internet available")
|
||||
|
||||
try:
|
||||
grid.update_data(agent.last_session)
|
||||
except Exception as e:
|
||||
logging.error("error connecting to the pwngrid-peer service: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
if self.lock.locked():
|
||||
return
|
||||
|
||||
try:
|
||||
self.check_inbox(agent)
|
||||
except Exception as e:
|
||||
logging.error("[grid] error while checking inbox: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
with self.lock:
|
||||
try:
|
||||
grid.update_data(agent.last_session)
|
||||
except Exception as e:
|
||||
logging.error("error connecting to the pwngrid-peer service: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
return
|
||||
|
||||
try:
|
||||
self.check_handshakes(agent)
|
||||
except Exception as e:
|
||||
logging.error("[grid] error while checking pcaps: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
try:
|
||||
self.check_inbox(agent)
|
||||
except Exception as e:
|
||||
logging.error("[grid] error while checking inbox: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
|
||||
try:
|
||||
self.check_handshakes(agent)
|
||||
except Exception as e:
|
||||
logging.error("[grid] error while checking pcaps: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
|
273
pwnagotchi/plugins/default/logtail.py
Normal file
273
pwnagotchi/plugins/default/logtail.py
Normal file
@ -0,0 +1,273 @@
|
||||
import os
|
||||
import logging
|
||||
import threading
|
||||
from itertools import islice
|
||||
from time import sleep
|
||||
from datetime import datetime,timedelta
|
||||
from pwnagotchi import plugins
|
||||
from pwnagotchi.utils import StatusFile
|
||||
from flask import render_template_string
|
||||
from flask import jsonify
|
||||
from flask import abort
|
||||
from flask import Response
|
||||
|
||||
|
||||
TEMPLATE = """
|
||||
{% extends "base.html" %}
|
||||
{% set active_page = "plugins" %}
|
||||
{% block title %}
|
||||
Logtail
|
||||
{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
{{ super() }}
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#filter {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
padding: 12px 20px 12px 40px;
|
||||
border: 1px solid #ddd;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
th, td {
|
||||
text-align: left;
|
||||
padding: 12px;
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
td:nth-child(2) {
|
||||
text-align: center;
|
||||
}
|
||||
thead, tr:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
tr {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
div.sticky {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
div.sticky > * {
|
||||
display: table-cell;
|
||||
}
|
||||
div.sticky > span {
|
||||
width: 1%;
|
||||
}
|
||||
div.sticky > input {
|
||||
width: 100%;
|
||||
}
|
||||
tr.default {
|
||||
color: black;
|
||||
}
|
||||
tr.info {
|
||||
color: black;
|
||||
}
|
||||
tr.warning {
|
||||
color: darkorange;
|
||||
}
|
||||
tr.error {
|
||||
color: crimson;
|
||||
}
|
||||
tr.debug {
|
||||
color: blueviolet;
|
||||
}
|
||||
.ui-mobile .ui-page-active {
|
||||
overflow: visible;
|
||||
overflow-x: visible;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
var table = document.getElementById('table');
|
||||
var filter = document.getElementById('filter');
|
||||
var filterVal = filter.value.toUpperCase();
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '{{ url_for('plugins') }}/logtail/stream');
|
||||
xhr.send();
|
||||
var position = 0;
|
||||
var data;
|
||||
var time;
|
||||
var level;
|
||||
var msg;
|
||||
var colorClass;
|
||||
|
||||
function handleNewData() {
|
||||
var messages = xhr.responseText.split('\\n');
|
||||
filterVal = filter.value.toUpperCase();
|
||||
messages.slice(position, -1).forEach(function(value) {
|
||||
|
||||
if (value.charAt(0) != '[') {
|
||||
msg = value;
|
||||
time = '';
|
||||
level = '';
|
||||
} else {
|
||||
data = value.split(']');
|
||||
time = data.shift() + ']';
|
||||
level = data.shift() + ']';
|
||||
msg = data.join(']');
|
||||
|
||||
switch(level) {
|
||||
case ' [INFO]':
|
||||
colorClass = 'info';
|
||||
break;
|
||||
case ' [WARNING]':
|
||||
colorClass = 'warning';
|
||||
break;
|
||||
case ' [ERROR]':
|
||||
colorClass = 'error';
|
||||
break;
|
||||
case ' [DEBUG]':
|
||||
colorClass = 'debug';
|
||||
break;
|
||||
default:
|
||||
colorClass = 'default';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var tr = document.createElement('tr');
|
||||
var td1 = document.createElement('td');
|
||||
var td2 = document.createElement('td');
|
||||
var td3 = document.createElement('td');
|
||||
|
||||
td1.textContent = time;
|
||||
td2.textContent = level;
|
||||
td3.textContent = msg;
|
||||
|
||||
tr.appendChild(td1);
|
||||
tr.appendChild(td2);
|
||||
tr.appendChild(td3);
|
||||
|
||||
tr.className = colorClass;
|
||||
|
||||
if (filterVal.length > 0 && value.toUpperCase().indexOf(filterVal) == -1) {
|
||||
tr.style.display = "none";
|
||||
}
|
||||
|
||||
table.appendChild(tr);
|
||||
});
|
||||
position = messages.length - 1;
|
||||
}
|
||||
|
||||
var scrollingElement = (document.scrollingElement || document.body)
|
||||
function scrollToBottom () {
|
||||
scrollingElement.scrollTop = scrollingElement.scrollHeight;
|
||||
}
|
||||
|
||||
var timer;
|
||||
var scrollElm = document.getElementById('autoscroll');
|
||||
timer = setInterval(function() {
|
||||
handleNewData();
|
||||
if (scrollElm.checked) {
|
||||
scrollToBottom();
|
||||
}
|
||||
if (xhr.readyState == XMLHttpRequest.DONE) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
var typingTimer;
|
||||
var doneTypingInterval = 1000;
|
||||
|
||||
filter.onkeyup = function() {
|
||||
clearTimeout(typingTimer);
|
||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||
}
|
||||
|
||||
filter.onkeydown = function() {
|
||||
clearTimeout(typingTimer);
|
||||
}
|
||||
|
||||
function doneTyping() {
|
||||
document.body.style.cursor = 'progress';
|
||||
var tr, tds, td, i, txtValue;
|
||||
filterVal = filter.value.toUpperCase();
|
||||
tr = table.getElementsByTagName("tr");
|
||||
for (i = 1; i < tr.length; i++) {
|
||||
txtValue = tr[i].textContent || tr[i].innerText;
|
||||
if (txtValue.toUpperCase().indexOf(filterVal) > -1) {
|
||||
tr[i].style.display = "table-row";
|
||||
} else {
|
||||
tr[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
document.body.style.cursor = 'default';
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="sticky">
|
||||
<input type="text" id="filter" placeholder="Search for ..." title="Type in a filter">
|
||||
<span><input checked type="checkbox" id="autoscroll"></span>
|
||||
<span><label for="autoscroll"> Autoscroll to bottom</label><br></span>
|
||||
</div>
|
||||
<table id="table">
|
||||
<thead>
|
||||
<th>
|
||||
Time
|
||||
</th>
|
||||
<th>
|
||||
Level
|
||||
</th>
|
||||
<th>
|
||||
Message
|
||||
</th>
|
||||
</thead>
|
||||
</table>
|
||||
{% endblock %}
|
||||
"""
|
||||
|
||||
|
||||
class Logtail(plugins.Plugin):
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '0.1.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin tails the logfile.'
|
||||
|
||||
def __init__(self):
|
||||
self.lock = threading.Lock()
|
||||
self.options = dict()
|
||||
self.ready = False
|
||||
|
||||
def on_config_changed(self, config):
|
||||
self.config = config
|
||||
self.ready = True
|
||||
|
||||
def on_loaded(self):
|
||||
"""
|
||||
Gets called when the plugin gets loaded
|
||||
"""
|
||||
logging.info("Logtail plugin loaded.")
|
||||
|
||||
|
||||
def on_webhook(self, path, request):
|
||||
if not self.ready:
|
||||
return "Plugin not ready"
|
||||
|
||||
if not path or path == "/":
|
||||
return render_template_string(TEMPLATE)
|
||||
|
||||
if path == 'stream':
|
||||
def generate():
|
||||
with open(self.config['main']['log']['path']) as f:
|
||||
yield ''.join(f.readlines()[-self.options.get('max-lines', 4096):])
|
||||
while True:
|
||||
yield f.readline()
|
||||
|
||||
return Response(generate(), mimetype='text/plain')
|
||||
|
||||
abort(404)
|
@ -1,6 +1,6 @@
|
||||
# memtemp shows memory infos and cpu temperature
|
||||
#
|
||||
# mem usage, cpu load, cpu temp
|
||||
# mem usage, cpu load, cpu temp, cpu frequency
|
||||
#
|
||||
###############################################################
|
||||
#
|
||||
@ -16,8 +16,15 @@
|
||||
# - Added CPU load
|
||||
# - Added horizontal and vertical orientation
|
||||
#
|
||||
# 19-09-2020 by crahan <crahan@n00.be>
|
||||
# - Added CPU frequency
|
||||
# - Made field types and order configurable (max 3 fields)
|
||||
# - Made line spacing and position configurable
|
||||
# - Updated code to dynamically generate UI elements
|
||||
# - Changed horizontal UI elements to Text
|
||||
# - Updated to version 1.0.2
|
||||
###############################################################
|
||||
from pwnagotchi.ui.components import LabeledValue
|
||||
from pwnagotchi.ui.components import LabeledValue, Text
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
import pwnagotchi.plugins as plugins
|
||||
@ -27,54 +34,31 @@ import logging
|
||||
|
||||
class MemTemp(plugins.Plugin):
|
||||
__author__ = 'https://github.com/xenDE'
|
||||
__version__ = '1.0.1'
|
||||
__version__ = '1.0.2'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'A plugin that will display memory/cpu usage and temperature'
|
||||
|
||||
ALLOWED_FIELDS = {
|
||||
'mem': 'mem_usage',
|
||||
'cpu': 'cpu_load',
|
||||
'temp': 'cpu_temp',
|
||||
'freq': 'cpu_freq'
|
||||
}
|
||||
DEFAULT_FIELDS = ['mem', 'cpu', 'temp']
|
||||
LINE_SPACING = 10
|
||||
LABEL_SPACING = 0
|
||||
FIELD_WIDTH = 4
|
||||
|
||||
def on_loaded(self):
|
||||
logging.info("memtemp plugin loaded.")
|
||||
|
||||
def mem_usage(self):
|
||||
return int(pwnagotchi.mem_usage() * 100)
|
||||
return f"{int(pwnagotchi.mem_usage() * 100)}%"
|
||||
|
||||
def cpu_load(self):
|
||||
return int(pwnagotchi.cpu_load() * 100)
|
||||
return f"{int(pwnagotchi.cpu_load() * 100)}%"
|
||||
|
||||
def on_ui_setup(self, ui):
|
||||
if ui.is_waveshare_v2():
|
||||
h_pos = (180, 80)
|
||||
v_pos = (180, 61)
|
||||
elif ui.is_waveshare_v1():
|
||||
h_pos = (170, 80)
|
||||
v_pos = (170, 61)
|
||||
elif ui.is_waveshare144lcd():
|
||||
h_pos = (53, 77)
|
||||
v_pos = (78, 67)
|
||||
elif ui.is_inky():
|
||||
h_pos = (140, 68)
|
||||
v_pos = (165, 54)
|
||||
elif ui.is_waveshare27inch():
|
||||
h_pos = (192, 138)
|
||||
v_pos = (216, 122)
|
||||
else:
|
||||
h_pos = (155, 76)
|
||||
v_pos = (180, 61)
|
||||
|
||||
if self.options['orientation'] == "vertical":
|
||||
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
|
||||
position=v_pos,
|
||||
label_font=fonts.Small, text_font=fonts.Small))
|
||||
else:
|
||||
# default to horizontal
|
||||
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
|
||||
position=h_pos,
|
||||
label_font=fonts.Small, text_font=fonts.Small))
|
||||
|
||||
def on_unload(self, ui):
|
||||
with ui._lock:
|
||||
ui.remove_element('memtemp')
|
||||
|
||||
def on_ui_update(self, ui):
|
||||
def cpu_temp(self):
|
||||
if self.options['scale'] == "fahrenheit":
|
||||
temp = (pwnagotchi.temperature() * 9 / 5) + 32
|
||||
symbol = "f"
|
||||
@ -85,11 +69,116 @@ class MemTemp(plugins.Plugin):
|
||||
# default to celsius
|
||||
temp = pwnagotchi.temperature()
|
||||
symbol = "c"
|
||||
return f"{temp}{symbol}"
|
||||
|
||||
def cpu_freq(self):
|
||||
with open('/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq', 'rt') as fp:
|
||||
return f"{round(float(fp.readline())/1000000, 1)}G"
|
||||
|
||||
def pad_text(self, data):
|
||||
return " " * (self.FIELD_WIDTH - len(data)) + data
|
||||
|
||||
def on_ui_setup(self, ui):
|
||||
try:
|
||||
# Configure field list
|
||||
self.fields = self.options['fields'].split(',')
|
||||
self.fields = [x.strip() for x in self.fields if x.strip() in self.ALLOWED_FIELDS.keys()]
|
||||
self.fields = self.fields[:3] # limit to the first 3 fields
|
||||
except Exception:
|
||||
# Set default value
|
||||
self.fields = self.DEFAULT_FIELDS
|
||||
|
||||
try:
|
||||
# Configure line_spacing
|
||||
line_spacing = int(self.options['linespacing'])
|
||||
except Exception:
|
||||
# Set default value
|
||||
line_spacing = self.LINE_SPACING
|
||||
|
||||
try:
|
||||
# Configure position
|
||||
pos = self.options['position'].split(',')
|
||||
pos = [int(x.strip()) for x in pos]
|
||||
if self.options['orientation'] == "vertical":
|
||||
v_pos = (pos[0], pos[1])
|
||||
else:
|
||||
h_pos = (pos[0], pos[1])
|
||||
except Exception:
|
||||
# Set default position based on screen type
|
||||
if ui.is_waveshare_v2():
|
||||
h_pos = (178, 84)
|
||||
v_pos = (197, 74)
|
||||
elif ui.is_waveshare_v1():
|
||||
h_pos = (170, 80)
|
||||
v_pos = (165, 61)
|
||||
elif ui.is_waveshare144lcd():
|
||||
h_pos = (53, 77)
|
||||
v_pos = (73, 67)
|
||||
elif ui.is_inky():
|
||||
h_pos = (140, 68)
|
||||
v_pos = (160, 54)
|
||||
elif ui.is_waveshare27inch():
|
||||
h_pos = (192, 138)
|
||||
v_pos = (211, 122)
|
||||
else:
|
||||
h_pos = (155, 76)
|
||||
v_pos = (175, 61)
|
||||
|
||||
if self.options['orientation'] == "vertical":
|
||||
ui.set('memtemp',
|
||||
" mem:%s%%\n cpu:%s%%\ntemp:%s%s" % (self.mem_usage(), self.cpu_load(), temp, symbol))
|
||||
# Dynamically create the required LabeledValue objects
|
||||
for idx, field in enumerate(self.fields):
|
||||
v_pos_x = v_pos[0]
|
||||
v_pos_y = v_pos[1] + ((len(self.fields) - 3) * -1 * line_spacing)
|
||||
ui.add_element(
|
||||
f"memtemp_{field}",
|
||||
LabeledValue(
|
||||
color=BLACK,
|
||||
label=f"{self.pad_text(field)}:",
|
||||
value="-",
|
||||
position=(v_pos_x, v_pos_y + (idx * line_spacing)),
|
||||
label_font=fonts.Small,
|
||||
text_font=fonts.Small,
|
||||
label_spacing=self.LABEL_SPACING,
|
||||
)
|
||||
)
|
||||
else:
|
||||
# default to horizontal
|
||||
ui.set('memtemp',
|
||||
" mem cpu temp\n %s%% %s%% %s%s" % (self.mem_usage(), self.cpu_load(), temp, symbol))
|
||||
h_pos_x = h_pos[0] + ((len(self.fields) - 3) * -1 * 25)
|
||||
h_pos_y = h_pos[1]
|
||||
ui.add_element(
|
||||
'memtemp_header',
|
||||
Text(
|
||||
color=BLACK,
|
||||
value=" ".join([self.pad_text(x) for x in self.fields]),
|
||||
position=(h_pos_x, h_pos_y),
|
||||
font=fonts.Small,
|
||||
)
|
||||
)
|
||||
ui.add_element(
|
||||
'memtemp_data',
|
||||
Text(
|
||||
color=BLACK,
|
||||
value=" ".join([self.pad_text("-") for x in self.fields]),
|
||||
position=(h_pos_x, h_pos_y + line_spacing),
|
||||
font=fonts.Small,
|
||||
)
|
||||
)
|
||||
|
||||
def on_unload(self, ui):
|
||||
with ui._lock:
|
||||
if self.options['orientation'] == "vertical":
|
||||
for idx, field in enumerate(self.fields):
|
||||
ui.remove_element(f"memtemp_{field}")
|
||||
else:
|
||||
# default to horizontal
|
||||
ui.remove_element('memtemp_header')
|
||||
ui.remove_element('memtemp_data')
|
||||
|
||||
def on_ui_update(self, ui):
|
||||
if self.options['orientation'] == "vertical":
|
||||
for idx, field in enumerate(self.fields):
|
||||
ui.set(f"memtemp_{field}", getattr(self, self.ALLOWED_FIELDS[field])())
|
||||
else:
|
||||
# default to horizontal
|
||||
data = " ".join([self.pad_text(getattr(self, self.ALLOWED_FIELDS[x])()) for x in self.fields])
|
||||
ui.set('memtemp_data', data)
|
||||
|
@ -7,18 +7,18 @@ import time
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.utils import StatusFile
|
||||
|
||||
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
|
||||
|
||||
|
||||
class NetPos(plugins.Plugin):
|
||||
__author__ = 'zenzen san'
|
||||
__version__ = '2.0.2'
|
||||
__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()
|
||||
@ -26,12 +26,14 @@ class NetPos(plugins.Plugin):
|
||||
self.lock = threading.Lock()
|
||||
|
||||
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 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()
|
||||
@ -47,6 +49,8 @@ class NetPos(plugins.Plugin):
|
||||
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()
|
||||
@ -124,7 +128,7 @@ class NetPos(plugins.Plugin):
|
||||
return netpos
|
||||
|
||||
def _get_geo_data(self, path, timeout=30):
|
||||
geourl = MOZILLA_API_URL.format(api=self.options['api_key'])
|
||||
geourl = self.API_URL.format(api=self.options['api_key'])
|
||||
|
||||
try:
|
||||
with open(path, "r") as json_file:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user