mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
fb7ef7b1dd | |||
28ff1429ac | |||
68941abc08 | |||
a7379d18e6 | |||
f9318ef27a | |||
cedb3d35ff | |||
4861a0c08b | |||
766463280d | |||
29e278da57 | |||
7e6d198c9b |
36
.github/ISSUE_TEMPLATE.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!--- Provide a general summary of the issue in the Title above -->
|
||||||
|
|
||||||
|
## Expected Behaviour
|
||||||
|
<!--- If you're describing a bug, tell us what should happen -->
|
||||||
|
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||||
|
|
||||||
|
## Current Behaviour
|
||||||
|
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||||
|
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||||
|
|
||||||
|
## Possible Solution
|
||||||
|
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||||
|
<!--- or ideas how to implement the addition or change -->
|
||||||
|
|
||||||
|
## Steps to Reproduce (for bugs)
|
||||||
|
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||||
|
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||||
|
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||||
|
|
||||||
|
## Your Environment
|
||||||
|
|
||||||
|
- [ ] You're using the official images
|
||||||
|
|
||||||
|
- [ ] You're using a raspberry pi 0
|
||||||
|
|
||||||
|
- [ ] You're using a supported LCD
|
||||||
|
|
||||||
|
|
||||||
|
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
62
.github/ISSUE_TEMPLATE.yml
vendored
62
.github/ISSUE_TEMPLATE.yml
vendored
@ -1,62 +0,0 @@
|
|||||||
name: Bug Report
|
|
||||||
description: File a bug report.
|
|
||||||
title: "[Bug]: "
|
|
||||||
labels: ["bug", "triage"]
|
|
||||||
projects: ["pwnagotchi"]
|
|
||||||
assignees:
|
|
||||||
- jayofelony
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thanks for taking the time to fill out this bug report!
|
|
||||||
- type: input
|
|
||||||
id: contact
|
|
||||||
attributes:
|
|
||||||
label: Contact Details
|
|
||||||
description: How can we get in touch with you if we need more info?
|
|
||||||
placeholder: ex. email@example.com
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
id: what-happened
|
|
||||||
attributes:
|
|
||||||
label: What happened?
|
|
||||||
description: Also tell us, what did you expect to happen?
|
|
||||||
placeholder: Tell us what you see!
|
|
||||||
value: "A bug happened!"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: dropdown
|
|
||||||
id: version
|
|
||||||
attributes:
|
|
||||||
label: Version
|
|
||||||
description: What version of our software are you running?
|
|
||||||
options:
|
|
||||||
- 2.9.4-2
|
|
||||||
default: 0
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: dropdown
|
|
||||||
id: 3rd-party-hardware
|
|
||||||
attributes:
|
|
||||||
label: 3rd Party Hardware
|
|
||||||
description: Are you using any 3rd party hardware? By selecting "Yes", you agree that you have tested the issue without the 3rd party hardware. And acknowledge that the issue may be related to the 3rd party hardware, for which we cannot provide full support.
|
|
||||||
options:
|
|
||||||
- "Yes"
|
|
||||||
- "No"
|
|
||||||
default: 1
|
|
||||||
- type: textarea
|
|
||||||
id: logs
|
|
||||||
attributes:
|
|
||||||
label: Relevant log output
|
|
||||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
|
||||||
render: shell
|
|
||||||
- type: checkboxes
|
|
||||||
id: terms
|
|
||||||
attributes:
|
|
||||||
label: Code of Conduct
|
|
||||||
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/jayofelony/pwnagotchi/blob/noai/CODE_OF_CONDUCT.md).
|
|
||||||
options:
|
|
||||||
- label: I agree to follow this project's Code of Conduct
|
|
||||||
required: true
|
|
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: "[BUG]"
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. ...
|
||||||
|
2. ...
|
||||||
|
3. ...
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Environment (please complete the following information):**
|
||||||
|
- Pwnagotchi version
|
||||||
|
- OS version
|
||||||
|
- Type of hardware
|
||||||
|
- Any additional hardware used
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
62
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
62
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,62 +0,0 @@
|
|||||||
name: Bug Report
|
|
||||||
description: File a bug report.
|
|
||||||
title: "[Bug]: "
|
|
||||||
labels: ["bug", "triage"]
|
|
||||||
projects: ["pwnagotchi"]
|
|
||||||
assignees:
|
|
||||||
- jayofelony
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thanks for taking the time to fill out this bug report!
|
|
||||||
- type: input
|
|
||||||
id: contact
|
|
||||||
attributes:
|
|
||||||
label: Contact Details
|
|
||||||
description: How can we get in touch with you if we need more info?
|
|
||||||
placeholder: ex. email@example.com
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
id: what-happened
|
|
||||||
attributes:
|
|
||||||
label: What happened?
|
|
||||||
description: Also tell us, what did you expect to happen?
|
|
||||||
placeholder: Tell us what you see!
|
|
||||||
value: "A bug happened!"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: dropdown
|
|
||||||
id: version
|
|
||||||
attributes:
|
|
||||||
label: Version
|
|
||||||
description: What version of our software are you running?
|
|
||||||
options:
|
|
||||||
- 2.9.4-2
|
|
||||||
default: 0
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: dropdown
|
|
||||||
id: 3rd-party-hardware
|
|
||||||
attributes:
|
|
||||||
label: 3rd Party Hardware
|
|
||||||
description: Are you using any 3rd party hardware? By selecting "Yes", you agree that you have tested the issue without the 3rd party hardware. And acknowledge that the issue may be related to the 3rd party hardware, for which we cannot provide full support.
|
|
||||||
options:
|
|
||||||
- "Yes"
|
|
||||||
- "No"
|
|
||||||
default: 1
|
|
||||||
- type: textarea
|
|
||||||
id: logs
|
|
||||||
attributes:
|
|
||||||
label: Relevant log output
|
|
||||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
|
||||||
render: shell
|
|
||||||
- type: checkboxes
|
|
||||||
id: terms
|
|
||||||
attributes:
|
|
||||||
label: Code of Conduct
|
|
||||||
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/jayofelony/pwnagotchi/blob/noai/CODE_OF_CONDUCT.md).
|
|
||||||
options:
|
|
||||||
- label: I agree to follow this project's Code of Conduct
|
|
||||||
required: true
|
|
59
.github/PULL_REQUEST_TEMPLATE.yml
vendored
59
.github/PULL_REQUEST_TEMPLATE.yml
vendored
@ -1,59 +0,0 @@
|
|||||||
name: Pull Request Template
|
|
||||||
description: Template for submitting pull requests.
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
## Description
|
|
||||||
Provide a detailed description of your changes here.
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: motivation_and_context
|
|
||||||
attributes:
|
|
||||||
label: Motivation and Context
|
|
||||||
description: Why is this change necessary? What problem does it solve?
|
|
||||||
placeholder: Add details here. Link to open issues if applicable.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: issue_proposal
|
|
||||||
attributes:
|
|
||||||
label: Did you raise an issue for this change? ([required](https://github.com/evilsocket/pwnagotchi/blob/master/CONTRIBUTING.md))
|
|
||||||
options:
|
|
||||||
- label: "Yes, I raised an issue."
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: testing
|
|
||||||
attributes:
|
|
||||||
label: How has this been tested?
|
|
||||||
description: Provide a detailed description of how your changes were tested.
|
|
||||||
placeholder: Add details about your testing environment, the tests you ran, and the impact of your changes.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: types_of_changes
|
|
||||||
attributes:
|
|
||||||
label: What types of changes does this Pull Request introduce?
|
|
||||||
options:
|
|
||||||
- label: "Bug fix (non-breaking change that fixes an issue)"
|
|
||||||
- label: "New feature (non-breaking change that adds functionality)"
|
|
||||||
- label: "Breaking change (fix or feature that affects existing functionality)"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: checklist
|
|
||||||
attributes:
|
|
||||||
label: Verify the following
|
|
||||||
options:
|
|
||||||
- label: "My code follows the code style of this project."
|
|
||||||
- label: "My change requires a change to the documentation."
|
|
||||||
- label: "I have updated the documentation accordingly."
|
|
||||||
- label: "I have read the [CONTRIBUTION](https://github.com/evilsocket/pwnagotchi/blob/master/CONTRIBUTING.md) guide."
|
|
||||||
- label: "I have signed off my commits with `git commit -s`."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
55
.github/workflows/publish.yml
vendored
Normal file
55
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
name: Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: ${{ matrix.name }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: "Raspberry Pi 32-bit"
|
||||||
|
id: "32bit"
|
||||||
|
- name: "Raspberry Pi 64-bit"
|
||||||
|
id: "64bit"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: publish/build
|
||||||
|
|
||||||
|
- name: Extract version from file
|
||||||
|
id: get_version
|
||||||
|
run: |
|
||||||
|
VERSION=$(cut -d "'" -f2 < publish/build/pwnagotchi/_version.py)
|
||||||
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install qemu dependencies
|
||||||
|
run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y
|
||||||
|
|
||||||
|
- name: Build ${{ matrix.name }} img file
|
||||||
|
run: cd publish/build; ls -la .; pwd; make packer; make ${{ matrix.id }}
|
||||||
|
|
||||||
|
- name: Change name of .img.xz to add version
|
||||||
|
run: |
|
||||||
|
sudo chown runner:docker "pwnagotchi-${{ matrix.id }}.img"
|
||||||
|
mv "pwnagotchi-${{ matrix.id }}.img" "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img"
|
||||||
|
|
||||||
|
- name: PiShrink
|
||||||
|
run: |
|
||||||
|
wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
|
||||||
|
chmod +x pishrink.sh
|
||||||
|
sudo mv pishrink.sh /usr/local/bin
|
||||||
|
sudo pishrink.sh -aZ "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img"
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
prerelease: false
|
||||||
|
make_latest: true
|
||||||
|
tag_name: v${{ env.VERSION }}
|
||||||
|
name: Pwnagotchi v${{ env.VERSION }}
|
||||||
|
files: pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img.xz
|
||||||
|
generate_release_notes: true
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
|
|
||||||
*.pyc
|
*.pyc
|
1
.idea/pwnagotchi.iml
generated
1
.idea/pwnagotchi.iml
generated
@ -2,7 +2,6 @@
|
|||||||
<module type="PYTHON_MODULE" version="4">
|
<module type="PYTHON_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.12 (pwnagotchi)" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.12 (pwnagotchi)" jdkType="Python SDK" />
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
exclude *.pyc .gitignore MANIFEST.in
|
exclude *.pyc .DS_Store .gitignore MANIFEST.in
|
||||||
|
include setup.py
|
||||||
|
include distribute_setup.py
|
||||||
include README.md
|
include README.md
|
||||||
include LICENSE
|
include LICENSE
|
||||||
|
recursive-include bin *
|
||||||
recursive-include pwnagotchi *.py
|
recursive-include pwnagotchi *.py
|
||||||
recursive-include pwnagotchi *.yml
|
recursive-include pwnagotchi *.yml
|
||||||
recursive-include pwnagotchi *.*
|
recursive-include pwnagotchi *.*
|
||||||
|
51
Makefile
51
Makefile
@ -1,3 +1,33 @@
|
|||||||
|
PACKER_VERSION := 1.11.0
|
||||||
|
PWN_HOSTNAME := pwnagotchi
|
||||||
|
PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# sudo apt-get install qemu-user-static qemu-utils
|
||||||
|
all: packer image
|
||||||
|
|
||||||
update_langs:
|
update_langs:
|
||||||
@for lang in pwnagotchi/locale/*/; do\
|
@for lang in pwnagotchi/locale/*/; do\
|
||||||
echo "updating language: $$lang ..."; \
|
echo "updating language: $$lang ..."; \
|
||||||
@ -9,3 +39,24 @@ compile_langs:
|
|||||||
echo "compiling language: $$lang ..."; \
|
echo "compiling language: $$lang ..."; \
|
||||||
./scripts/language.sh compile $$(basename $$lang); \
|
./scripts/language.sh compile $$(basename $$lang); \
|
||||||
done
|
done
|
||||||
|
|
||||||
|
packer:
|
||||||
|
curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip
|
||||||
|
unzip -o /tmp/packer.zip -d /tmp
|
||||||
|
sudo mv /tmp/packer /usr/bin/packer
|
||||||
|
|
||||||
|
image: packer
|
||||||
|
export LC_ALL=en_GB.UTF-8
|
||||||
|
cd builder && sudo /usr/bin/packer init combined.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" combined.json.pkr.hcl
|
||||||
|
|
||||||
|
32bit: packer
|
||||||
|
export LC_ALL=en_GB.UTF-8
|
||||||
|
cd builder && sudo /usr/bin/packer init raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi32.json.pkr.hcl
|
||||||
|
|
||||||
|
64bit: packer
|
||||||
|
export LC_ALL=en_GB.UTF-8
|
||||||
|
cd builder && sudo /usr/bin/packer init raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi64.json.pkr.hcl
|
||||||
|
|
||||||
|
clean:
|
||||||
|
- rm -rf /tmp/packer*
|
||||||
|
- rm -rf /tmp/LICENSE.txt
|
||||||
|
20
README.md
20
README.md
@ -5,36 +5,36 @@ This is the main source for all forks:
|
|||||||
|
|
||||||
**For installation docs check out the [wiki](https://github.com/jayofelony/pwnagotchi/wiki)!**
|
**For installation docs check out the [wiki](https://github.com/jayofelony/pwnagotchi/wiki)!**
|
||||||
|
|
||||||
If you want to sponsor this project you can use GH Sponsor or cryptocurrency:
|
|
||||||
|
|
||||||
[GH Sponsor](https://github.com/sponsors/jayofelony)
|
[GH Sponsor](https://github.com/sponsors/jayofelony)
|
||||||
|
|
||||||
Or send some ethereum: 0x33ceC4Abe80fDE460a924d596d4dE31Bc0767bb6
|
|
||||||
|
|
||||||
**Proudly partnering with [PiSugar](https://www.pisugar.com)!!**
|
**Proudly partnering with [PiSugar](https://www.pisugar.com)!!**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[Pwnagotchi](https://pwnagotchi.org/) is a Raspberry Pi leveraging [bettercap](https://www.bettercap.org/) that survives from its surrounding Wi-Fi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
|
[Pwnagotchi](https://pwnagotchi.org/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding Wi-Fi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
|
||||||
full and half WPA handshakes.
|
full and half WPA handshakes.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The "old" Pwnagotchi used to have AI to help it learn from its environment, but since then AI seemed to destabilize the Wi-Fi firmware. So I have chosen to remove the AI completely to give the Pwnagotchi more up-time and longer battery life when taking it on a walk.
|
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 Wi-Fi things to** in the environments you expose it to.
|
||||||
|
|
||||||
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.
|
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 Wi-Fi 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.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
https://github.com/jayofelony/pwnagotchi/wiki
|
https://github.com/jayofelony/pwnagotchi/wiki
|
||||||
https://pwnagotchi.org
|
https://www.pwnagotchi.org
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
| | Official Links |
|
| | Official Links |
|
||||||
|-----------|----------------------------------------------------------|
|
|-----------|-------------------------------------------------------------|
|
||||||
| Website | [pwnagotchi.org](https://pwnagotchi.org/) |
|
| Website | [pwnagotchi.org](https://pwnagotchi.org/) |
|
||||||
| Chat | [discord](https://discord.gg/PGgnzFbz4M) |
|
| Forum | [discord.gg](https://discord.gg/PGgnzFbz4M) |
|
||||||
| Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/) |
|
| Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/) |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
0
apt_packages.txt
Normal file
0
apt_packages.txt
Normal file
32
pwnagotchi/cli.py → bin/pwnagotchi
Normal file → Executable file
32
pwnagotchi/cli.py → bin/pwnagotchi
Normal file → Executable file
@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
import logging
|
import logging
|
||||||
import argparse
|
import argparse
|
||||||
import time
|
import time
|
||||||
@ -13,6 +14,7 @@ from pwnagotchi import utils
|
|||||||
from pwnagotchi.google import cmd as google_cmd
|
from pwnagotchi.google import cmd as google_cmd
|
||||||
from pwnagotchi.plugins import cmd as plugins_cmd
|
from pwnagotchi.plugins import cmd as plugins_cmd
|
||||||
from pwnagotchi import log
|
from pwnagotchi import log
|
||||||
|
from pwnagotchi import restart
|
||||||
from pwnagotchi import fs
|
from pwnagotchi import fs
|
||||||
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
|
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
|
||||||
|
|
||||||
@ -48,7 +50,6 @@ def pwnagotchi_cli():
|
|||||||
logging.info("entering auto mode ...")
|
logging.info("entering auto mode ...")
|
||||||
|
|
||||||
agent.mode = 'auto'
|
agent.mode = 'auto'
|
||||||
agent.last_session.parse(agent.view(), args.skip_session) # show stats in AUTO
|
|
||||||
agent.start()
|
agent.start()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@ -208,7 +209,6 @@ def pwnagotchi_cli():
|
|||||||
ssid = input("SSID (Name): ")
|
ssid = input("SSID (Name): ")
|
||||||
bssid = input("BSSID (MAC): ")
|
bssid = input("BSSID (MAC): ")
|
||||||
f.write(f"\t\"{ssid}\",\n")
|
f.write(f"\t\"{ssid}\",\n")
|
||||||
if bssid != "":
|
|
||||||
f.write(f"\t\"{bssid}\",\n")
|
f.write(f"\t\"{bssid}\",\n")
|
||||||
f.write("]\n")
|
f.write("]\n")
|
||||||
# set bluetooth tether
|
# set bluetooth tether
|
||||||
@ -216,24 +216,20 @@ def pwnagotchi_cli():
|
|||||||
"[Y/N] ")
|
"[Y/N] ")
|
||||||
if pwn_bluetooth.lower() in ('y', 'yes'):
|
if pwn_bluetooth.lower() in ('y', 'yes'):
|
||||||
f.write("main.plugins.bt-tether.enabled = true\n\n")
|
f.write("main.plugins.bt-tether.enabled = true\n\n")
|
||||||
pwn_bluetooth_phone_name = input("What name uses your phone, check settings?\n\n")
|
pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n"
|
||||||
if pwn_bluetooth_phone_name != "":
|
|
||||||
f.write(f"main.plugins.bt-tether.phone-name = \"{pwn_bluetooth_phone_name}\"\n")
|
|
||||||
pwn_bluetooth_device = input("What device do you use? android or ios?\n\n"
|
|
||||||
"Device: ")
|
"Device: ")
|
||||||
if pwn_bluetooth_device != "":
|
if pwn_bluetooth_device.lower() == "android":
|
||||||
if pwn_bluetooth_device != "android" and pwn_bluetooth_device != "ios":
|
f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n")
|
||||||
print("You have chosen an invalid device. Please start over.")
|
|
||||||
exit()
|
|
||||||
f.write(f"main.plugins.bt-tether.phone = \"{pwn_bluetooth_device.lower()}\"\n")
|
|
||||||
if pwn_bluetooth_device == "android":
|
|
||||||
f.write("main.plugins.bt-tether.ip = \"192.168.44.44\"\n")
|
|
||||||
elif pwn_bluetooth_device == "ios":
|
|
||||||
f.write("main.plugins.bt-tether.ip = \"172.20.10.6\"\n")
|
|
||||||
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
|
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
|
||||||
"MAC: ")
|
"MAC: ")
|
||||||
if pwn_bluetooth_mac != "":
|
if pwn_bluetooth_mac != "":
|
||||||
f.write(f"main.plugins.bt-tether.mac = \"{pwn_bluetooth_mac}\"\n")
|
f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||||
|
elif pwn_bluetooth_device.lower() == "ios":
|
||||||
|
f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n")
|
||||||
|
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
|
||||||
|
"MAC: ")
|
||||||
|
if pwn_bluetooth_mac != "":
|
||||||
|
f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||||
# set up display settings
|
# set up display settings
|
||||||
pwn_display_enabled = input("Do you want to enable a display?\n\n"
|
pwn_display_enabled = input("Do you want to enable a display?\n\n"
|
||||||
"[Y/N]: ")
|
"[Y/N]: ")
|
||||||
@ -255,9 +251,7 @@ def pwnagotchi_cli():
|
|||||||
if pwn_bluetooth.lower() in ('y', 'yes'):
|
if pwn_bluetooth.lower() in ('y', 'yes'):
|
||||||
if pwn_bluetooth_device.lower == "android":
|
if pwn_bluetooth_device.lower == "android":
|
||||||
print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n"
|
print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n"
|
||||||
"Be sure to run `sudo bluetoothctl` to set-up the bluetooth connection for the first time. And read the wiki step 4.\n"
|
|
||||||
"Your configuration is done, and I will restart in 5 seconds.")
|
"Your configuration is done, and I will restart in 5 seconds.")
|
||||||
|
|
||||||
elif pwn_bluetooth_device.lower == "ios":
|
elif pwn_bluetooth_device.lower == "ios":
|
||||||
print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n"
|
print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n"
|
||||||
"Your configuration is done, and I will restart in 5 seconds.")
|
"Your configuration is done, and I will restart in 5 seconds.")
|
||||||
@ -328,7 +322,7 @@ def pwnagotchi_cli():
|
|||||||
agent = Agent(view=display, config=config, keypair=KeyPair(view=display))
|
agent = Agent(view=display, config=config, keypair=KeyPair(view=display))
|
||||||
|
|
||||||
def usr1_handler(*unused):
|
def usr1_handler(*unused):
|
||||||
logging.info('Received USR1 signal. Restart process ...')
|
logging.info('Received USR1 singal. Restart process ...')
|
||||||
agent._restart("MANU" if args.do_manual else "AUTO")
|
agent._restart("MANU" if args.do_manual else "AUTO")
|
||||||
|
|
||||||
signal.signal(signal.SIGUSR1, usr1_handler)
|
signal.signal(signal.SIGUSR1, usr1_handler)
|
147
builder/combined.json.pkr.hcl
Normal file
147
builder/combined.json.pkr.hcl
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
arm-image = {
|
||||||
|
source = "github.com/solo-io/arm-image"
|
||||||
|
version = ">= 0.0.1"
|
||||||
|
}
|
||||||
|
ansible = {
|
||||||
|
source = "github.com/hashicorp/ansible"
|
||||||
|
version = ">= 1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_hostname" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_version" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm-image" "rpi64-pwnagotchi" {
|
||||||
|
image_type = "raspberrypi"
|
||||||
|
iso_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz"
|
||||||
|
iso_checksum = "sha256:43d150e7901583919e4eb1f0fa83fe0363af2d1e9777a5bb707d696d535e2599"
|
||||||
|
output_filename = "../../../pwnagotchi-64bit.img"
|
||||||
|
qemu_binary = "qemu-aarch64-static"
|
||||||
|
target_image_size = 19969908736
|
||||||
|
image_mounts = ["/boot/firmware","/"]
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm-image" "rpi32-pwnagotchi" {
|
||||||
|
image_type = "raspberrypi"
|
||||||
|
iso_url = "https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-07-04/2024-07-04-raspios-bookworm-armhf-lite.img.xz"
|
||||||
|
iso_checksum = "sha256:df9c192d66d35e1ce67acde33a5b5f2b81ff02d2b986ea52f1f6ea211d646a1b"
|
||||||
|
output_filename = "../../../pwnagotchi-32bit.img"
|
||||||
|
qemu_binary = "qemu-arm-static"
|
||||||
|
qemu_args = ["-cpu", "arm1176"]
|
||||||
|
image_arch = "arm"
|
||||||
|
target_image_size = 19969908736
|
||||||
|
image_mounts = ["/boot/firmware","/"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# a build block invokes sources and runs provisioning steps on them. The
|
||||||
|
# documentation for build blocks can be found here:
|
||||||
|
# https://www.packer.io/docs/from-1.5/blocks/build
|
||||||
|
build {
|
||||||
|
name = "Raspberry Pi 64 Pwnagotchi"
|
||||||
|
sources = ["source.arm-image.rpi64-pwnagotchi"]
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/bin/"
|
||||||
|
sources = [
|
||||||
|
"data/64bit/usr/bin/bettercap-launcher",
|
||||||
|
"data/64bit/usr/bin/hdmioff",
|
||||||
|
"data/64bit/usr/bin/hdmion",
|
||||||
|
"data/64bit/usr/bin/monstart",
|
||||||
|
"data/64bit/usr/bin/monstop",
|
||||||
|
"data/64bit/usr/bin/pwnagotchi-launcher",
|
||||||
|
"data/64bit/usr/bin/pwnlib",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /usr/bin/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/local/src/pwnagotchi/"
|
||||||
|
source = "../"
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/systemd/system/"
|
||||||
|
sources = [
|
||||||
|
"data/64bit/etc/systemd/system/bettercap.service",
|
||||||
|
"data/64bit/etc/systemd/system/pwnagotchi.service",
|
||||||
|
"data/64bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
|
source = "data/64bit/etc/update-motd.d/01-motd"
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||||
|
}
|
||||||
|
provisioner "ansible-local" {
|
||||||
|
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||||
|
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||||
|
playbook_file = "raspberrypi64.yml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build {
|
||||||
|
name = "Raspberry Pi 32 Pwnagotchi"
|
||||||
|
sources = ["source.arm-image.rpi32-pwnagotchi"]
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/bin/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/usr/bin/bettercap-launcher",
|
||||||
|
"data/32bit/usr/bin/hdmioff",
|
||||||
|
"data/32bit/usr/bin/hdmion",
|
||||||
|
"data/32bit/usr/bin/monstart",
|
||||||
|
"data/32bit/usr/bin/monstop",
|
||||||
|
"data/32bit/usr/bin/pwnagotchi-launcher",
|
||||||
|
"data/32bit/usr/bin/pwnlib",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/local/src/pwnagotchi/"
|
||||||
|
source = "../"
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /usr/bin/*"]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/systemd/system/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/etc/systemd/system/bettercap.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwnagotchi.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
|
source = "data/32bit/etc/update-motd.d/01-motd"
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||||
|
}
|
||||||
|
provisioner "ansible-local" {
|
||||||
|
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||||
|
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||||
|
playbook_file = "raspberrypi32.yml"
|
||||||
|
}
|
||||||
|
}
|
5
builder/data/etc/NetworkManager/NetworkManager.conf
Normal file
5
builder/data/etc/NetworkManager/NetworkManager.conf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[main]
|
||||||
|
plugins=keyfile,ifupdown
|
||||||
|
|
||||||
|
[ifupdown]
|
||||||
|
managed=true
|
36
builder/data/etc/bash_completion.d/pwnagotchi_completion.sh
Normal file
36
builder/data/etc/bash_completion.d/pwnagotchi_completion.sh
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
_show_complete()
|
||||||
|
{
|
||||||
|
local cur opts node_names all_options opt_line
|
||||||
|
all_options="
|
||||||
|
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --wizard --check-update --donate {plugins,google}
|
||||||
|
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
|
||||||
|
pwnagotchi plugins list -i --installed -h --help
|
||||||
|
pwnagotchi plugins install -h --help
|
||||||
|
pwnagotchi plugins uninstall -h --help
|
||||||
|
pwnagotchi plugins enable -h --help
|
||||||
|
pwnagotchi plugins disable -h --help
|
||||||
|
pwnagotchi plugins update -h --help
|
||||||
|
pwnagotchi plugins upgrade -h --help
|
||||||
|
pwnagotchi google -h --help {login,refresh}
|
||||||
|
pwnagotchi google login -h --help
|
||||||
|
pwnagotchi google refresh -h --help
|
||||||
|
"
|
||||||
|
COMPREPLY=()
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
# shellcheck disable=SC2124
|
||||||
|
cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}"
|
||||||
|
opt_line="$(grep -m1 "$cmd" <<<"$all_options")"
|
||||||
|
if [[ ${cur} == -* ]] ; then
|
||||||
|
opts="$(echo "$opt_line" | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')"
|
||||||
|
# shellcheck disable=SC2207
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')"
|
||||||
|
# shellcheck disable=SC2207
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _show_complete pwnagotchi
|
26
builder/data/etc/dphys-swapfile
Normal file
26
builder/data/etc/dphys-swapfile
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# /etc/dphys-swapfile - user settings for dphys-swapfile package
|
||||||
|
# author Neil Franklin, last modification 2010.05.05
|
||||||
|
# copyright ETH Zuerich Physics Departement
|
||||||
|
# use under either modified/non-advertising BSD or GPL license
|
||||||
|
|
||||||
|
# this file is sourced with . so full normal sh syntax applies
|
||||||
|
|
||||||
|
# the default settings are added as commented out CONF_*=* lines
|
||||||
|
|
||||||
|
|
||||||
|
# where we want the swapfile to be, this is the default
|
||||||
|
#CONF_SWAPFILE=/var/swap
|
||||||
|
|
||||||
|
# set size to absolute value, leaving empty (default) then uses computed value
|
||||||
|
# you most likely don't want this, unless you have an special disk situation
|
||||||
|
CONF_SWAPSIZE=2048
|
||||||
|
|
||||||
|
# set size to computed value, this times RAM size, dynamically adapts,
|
||||||
|
# guarantees that there is enough swap without wasting disk space on excess
|
||||||
|
#CONF_SWAPFACTOR=2
|
||||||
|
|
||||||
|
# restrict size (computed and absolute!) to maximally this limit
|
||||||
|
# can be set to empty for no limit, but beware of filled partitions!
|
||||||
|
# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it
|
||||||
|
# but is also sensible on 64bit to prevent filling /var or even / partition
|
||||||
|
#CONF_MAXSWAP=2048
|
6
builder/data/etc/modules-load.d/modules.conf
Normal file
6
builder/data/etc/modules-load.d/modules.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# /etc/modules: kernel modules to load at boot time.
|
||||||
|
#
|
||||||
|
# This file contains the names of kernel modules that should be loaded
|
||||||
|
# at boot time, one per line. Lines beginning with "#" are ignored.
|
||||||
|
# Parameters can be specified after the module name.
|
||||||
|
i2c-dev
|
2
builder/data/etc/network/interfaces.d/eth0-cfg
Normal file
2
builder/data/etc/network/interfaces.d/eth0-cfg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
allow-hotplug eth0
|
||||||
|
iface eth0 inet dhcp
|
2
builder/data/etc/network/interfaces.d/lo-cfg
Normal file
2
builder/data/etc/network/interfaces.d/lo-cfg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
auto lo
|
||||||
|
iface lo inet loopback
|
8
builder/data/etc/network/interfaces.d/usb0-cfg
Normal file
8
builder/data/etc/network/interfaces.d/usb0-cfg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
allow-hotplug usb0
|
||||||
|
iface usb0 inet static
|
||||||
|
address 10.0.0.2
|
||||||
|
netmask 255.255.255.0
|
||||||
|
network 10.0.0.0
|
||||||
|
broadcast 10.0.0.255
|
||||||
|
gateway 10.0.0.1
|
||||||
|
metric 101
|
2
builder/data/etc/network/interfaces.d/wlan0-cfg
Normal file
2
builder/data/etc/network/interfaces.d/wlan0-cfg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
allow-hotplug wlan0
|
||||||
|
iface wlan0 inet static
|
16
builder/data/etc/rc.local
Normal file
16
builder/data/etc/rc.local
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
#
|
||||||
|
# rc.local
|
||||||
|
#
|
||||||
|
# This script is executed at the end of each multiuser runlevel.
|
||||||
|
# Make sure that the script will "exit 0" on success or any other
|
||||||
|
# value on error.
|
||||||
|
#
|
||||||
|
# In order to enable or disable this script just change the execution
|
||||||
|
# bits.
|
||||||
|
#
|
||||||
|
# By default this script does nothing.
|
||||||
|
|
||||||
|
timedatectl set-ntp true
|
||||||
|
|
||||||
|
exit 0
|
13
builder/data/etc/systemd/system/bettercap.service
Normal file
13
builder/data/etc/systemd/system/bettercap.service
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=bettercap api.rest service.
|
||||||
|
Documentation=https://bettercap.org
|
||||||
|
Wants=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/bettercap-launcher
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
20
builder/data/etc/systemd/system/bluetooth.service
Normal file
20
builder/data/etc/systemd/system/bluetooth.service
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Bluetooth service
|
||||||
|
Documentation=man:bluetoothd(8)
|
||||||
|
ConditionPathIsDirectory=/sys/class/bluetooth
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=dbus
|
||||||
|
BusName=org.bluez
|
||||||
|
ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp
|
||||||
|
NotifyAccess=main
|
||||||
|
#WatchdogSec=10
|
||||||
|
#Restart=on-failure
|
||||||
|
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||||
|
LimitNPROC=1
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectSystem=full
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=bluetooth.target
|
||||||
|
Alias=dbus-org.bluez.service
|
19
builder/data/etc/systemd/system/pwnagotchi.service
Normal file
19
builder/data/etc/systemd/system/pwnagotchi.service
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
|
||||||
|
Documentation=https://pwnagotchi.org
|
||||||
|
Wants=network.target
|
||||||
|
After=pwngrid-peer.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=~
|
||||||
|
ExecStart=/usr/bin/pwnagotchi-launcher
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
|
TasksMax=infinity
|
||||||
|
LimitNPROC=infinity
|
||||||
|
StandardOutput=null
|
||||||
|
StandardError=null
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
16
builder/data/etc/systemd/system/pwngrid-peer.service
Normal file
16
builder/data/etc/systemd/system/pwngrid-peer.service
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=pwngrid peer service.
|
||||||
|
Documentation=https://pwnagotchi.ai
|
||||||
|
Wants=network.target
|
||||||
|
After=bettercap.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
|
||||||
|
Environment=LD_LIBRARY_PATH=/usr/local/lib
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /etc/pwnagotchi/log/pwngrid-peer.log -iface wlan0mon
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
34
builder/data/etc/update-motd.d/01-motd
Executable file
34
builder/data/etc/update-motd.d/01-motd
Executable file
@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
_hostname=$(hostname)
|
||||||
|
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.11/dist-packages/pwnagotchi/_version.py)
|
||||||
|
echo
|
||||||
|
echo "(◕‿‿◕) $_hostname"
|
||||||
|
echo
|
||||||
|
echo " Hi! I'm a pwnagotchi $_version, please take good care of me!"
|
||||||
|
echo " Here are some basic things you need to know to raise me properly!"
|
||||||
|
echo
|
||||||
|
echo " If you want to change my configuration, use /etc/pwnagotchi/config.toml"
|
||||||
|
echo " All plugin config files are located in /etc/pwnagotchi/conf.d/"
|
||||||
|
echo " Read the readme if you want to use gdrivesync plugin!!"
|
||||||
|
echo
|
||||||
|
echo " All the configuration options can be found on /etc/pwnagotchi/default.toml,"
|
||||||
|
echo " but don't change this file because I will recreate it every time I'm restarted!"
|
||||||
|
echo
|
||||||
|
echo " I use oPwnGrid as my main API, you can check stats at https://opwngrid.xyz"
|
||||||
|
echo
|
||||||
|
echo " I'm managed by systemd. Here are some basic commands."
|
||||||
|
echo
|
||||||
|
echo " If you want to know what I'm doing, you can check my logs with the command"
|
||||||
|
echo " - pwnlog"
|
||||||
|
echo " - sudo pwnagotchi --wizard, to help set up a config.toml"
|
||||||
|
echo " - sudo pwnagotchi --version, to check the current version"
|
||||||
|
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
|
||||||
|
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
||||||
|
echo
|
||||||
|
echo " If you want to know if I'm running, you can use"
|
||||||
|
echo " sudo systemctl status pwnagotchi"
|
||||||
|
echo
|
||||||
|
echo " You can restart me using"
|
||||||
|
echo " pwnkill"
|
||||||
|
echo
|
||||||
|
echo " You can learn more about me at https://pwnagotchi.org/"
|
0
builder/data/root/client_secrets.json
Normal file
0
builder/data/root/client_secrets.json
Normal file
15
builder/data/root/settings.yaml
Normal file
15
builder/data/root/settings.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
client_config_backend: file
|
||||||
|
client_config_file: /root/client_secrets.json
|
||||||
|
client_config:
|
||||||
|
client_id: <YOUR CLIENT ID>
|
||||||
|
client_secret: <YOUR CLIENT SECRET>
|
||||||
|
|
||||||
|
save_credentials: True
|
||||||
|
save_credentials_backend: file
|
||||||
|
save_credentials_file: /root/credentials.json
|
||||||
|
|
||||||
|
get_refresh_token: True
|
||||||
|
|
||||||
|
oauth_scope:
|
||||||
|
- https://www.googleapis.com/auth/drive
|
||||||
|
- https://www.googleapis.com/auth/drive.install
|
18
builder/data/usr/bin/bettercap-launcher
Executable file
18
builder/data/usr/bin/bettercap-launcher
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
start_monitor_interface
|
||||||
|
|
||||||
|
if is_auto_mode_no_delete; then
|
||||||
|
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface wlan0mon
|
||||||
|
else
|
||||||
|
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface wlan0mon
|
||||||
|
fi
|
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()
|
2
builder/data/usr/bin/hdmioff
Executable file
2
builder/data/usr/bin/hdmioff
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
sudo /usr/bin/tvservice -o
|
2
builder/data/usr/bin/hdmion
Executable file
2
builder/data/usr/bin/hdmion
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
sudo /usr/bin/tvservice -p
|
3
builder/data/usr/bin/monstart
Executable file
3
builder/data/usr/bin/monstart
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source /usr/bin/pwnlib
|
||||||
|
start_monitor_interface
|
3
builder/data/usr/bin/monstop
Executable file
3
builder/data/usr/bin/monstop
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source /usr/bin/pwnlib
|
||||||
|
stop_monitor_interface
|
17
builder/data/usr/bin/pwnagotchi-launcher
Executable file
17
builder/data/usr/bin/pwnagotchi-launcher
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source /usr/bin/pwnlib
|
||||||
|
|
||||||
|
# we need to decrypt something
|
||||||
|
if is_crypted_mode; then
|
||||||
|
while ! is_decrypted; do
|
||||||
|
echo "Waiting for decryption..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if is_auto_mode; then
|
||||||
|
/usr/local/src/pwnagotchi/env/bin/pwnagotchi
|
||||||
|
systemctl restart bettercap
|
||||||
|
else
|
||||||
|
/usr/local/src/pwnagotchi/env/bin/pwnagotchi --manual
|
||||||
|
fi
|
178
builder/data/usr/bin/pwnlib
Executable file
178
builder/data/usr/bin/pwnlib
Executable file
@ -0,0 +1,178 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# reload mod
|
||||||
|
reload_brcm() {
|
||||||
|
if ! modprobe -r brcmfmac; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
if ! modprobe brcmfmac; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
iw dev wlan0 set power_save off
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# starts mon0
|
||||||
|
start_monitor_interface() {
|
||||||
|
rfkill unblock all
|
||||||
|
ifconfig wlan0 up
|
||||||
|
sleep 3
|
||||||
|
iw dev wlan0 set power_save off
|
||||||
|
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor
|
||||||
|
sleep 2
|
||||||
|
rfkill unblock all
|
||||||
|
ifconfig wlan0 down
|
||||||
|
ifconfig wlan0mon up
|
||||||
|
iw dev wlan0mon set power_save off
|
||||||
|
}
|
||||||
|
|
||||||
|
# stops mon0
|
||||||
|
stop_monitor_interface() {
|
||||||
|
ifconfig wlan0mon down && iw dev wlan0mon del
|
||||||
|
reload_brcm
|
||||||
|
ifconfig wlan0 up
|
||||||
|
}
|
||||||
|
|
||||||
|
# returns 0 if the specified network interface is up
|
||||||
|
is_interface_up() {
|
||||||
|
if grep -qi 'up' /sys/class/net/"$1"/operstate; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# returns 0 if conditions for AUTO mode are met
|
||||||
|
is_auto_mode() {
|
||||||
|
# check override file first
|
||||||
|
if [ -f /root/.pwnagotchi-manual ]; then
|
||||||
|
# remove the override file if found
|
||||||
|
rm -rf /root/.pwnagotchi-manual
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check override file first
|
||||||
|
if [ -f /root/.pwnagotchi-auto ]; then
|
||||||
|
# remove the override file if found
|
||||||
|
rm -rf /root/.pwnagotchi-auto
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if usb0 is up, we're in MANU
|
||||||
|
if is_interface_up usb0; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if eth0 is up (for other boards), we're in MANU
|
||||||
|
if is_interface_up eth0; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# no override, but none of the interfaces is up -> AUTO
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# returns 0 if conditions for AUTO mode are met
|
||||||
|
is_auto_mode_no_delete() {
|
||||||
|
# check override file first
|
||||||
|
if [ -f /root/.pwnagotchi-manual ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check override file first
|
||||||
|
if [ -f /root/.pwnagotchi-auto ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if usb0 is up, we're in MANU
|
||||||
|
if is_interface_up usb0; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if eth0 is up (for other boards), we're in MANU
|
||||||
|
if is_interface_up eth0; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# no override, but none of the interfaces is up -> AUTO
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# check if we need to decrypt something
|
||||||
|
is_crypted_mode() {
|
||||||
|
if [ -f /root/.pwnagotchi-crypted ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# decryption loop
|
||||||
|
is_decrypted() {
|
||||||
|
while read -r mapping container mount; do
|
||||||
|
# mapping = name the device or file will be mapped to
|
||||||
|
# container = the luks encrypted device or file
|
||||||
|
# mount = the mountpoint
|
||||||
|
|
||||||
|
# fail if not mounted
|
||||||
|
if ! mountpoint -q "$mount" >/dev/null 2>&1; then
|
||||||
|
if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then
|
||||||
|
</tmp/.pwnagotchi-secret-"$mapping" read -r SECRET
|
||||||
|
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
|
||||||
|
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/dev/null 2>&1; then
|
||||||
|
echo "Container decrypted!"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then
|
||||||
|
echo "Mounted /dev/mapper/$mapping to $mount"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then
|
||||||
|
>/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pgrep -f decryption-webserver >/dev/null 2>&1; then
|
||||||
|
>/dev/null 2>&1 decryption-webserver &
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pgrep wpa_supplicant >/dev/null 2>&1; then
|
||||||
|
>/tmp/wpa_supplicant.conf cat <<EOF
|
||||||
|
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||||
|
update_config=1
|
||||||
|
ap_scan=2
|
||||||
|
|
||||||
|
network={
|
||||||
|
ssid="DECRYPT-ME"
|
||||||
|
mode=2
|
||||||
|
key_mgmt=WPA-PSK
|
||||||
|
psk="pwnagotchi"
|
||||||
|
frequency=2437
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
>/dev/null 2>&1 wpa_supplicant -u -s -O -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf &
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pgrep dnsmasq >/dev/null 2>&1; then
|
||||||
|
>/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h &
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done </root/.pwnagotchi-crypted
|
||||||
|
|
||||||
|
# overwrite passwords
|
||||||
|
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/dev/null
|
||||||
|
# delete
|
||||||
|
rm /tmp/.pwnagotchi-secret-*
|
||||||
|
sync # flush
|
||||||
|
|
||||||
|
pkill wpa_supplicant
|
||||||
|
pkill dnsmasq
|
||||||
|
pid="$(pgrep -f "decryption-webserver")"
|
||||||
|
[[ -n "$pid" ]] && kill "$pid"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
82
builder/raspberrypi32.json.pkr.hcl
Normal file
82
builder/raspberrypi32.json.pkr.hcl
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
arm-image = {
|
||||||
|
source = "github.com/solo-io/arm-image"
|
||||||
|
version = ">= 0.0.1"
|
||||||
|
}
|
||||||
|
ansible = {
|
||||||
|
source = "github.com/hashicorp/ansible"
|
||||||
|
version = ">= 1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_hostname" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_version" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm-image" "rpi32-pwnagotchi" {
|
||||||
|
image_type = "raspberrypi"
|
||||||
|
iso_url = "https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-07-04/2024-07-04-raspios-bookworm-armhf-lite.img.xz"
|
||||||
|
iso_checksum = "sha256:df9c192d66d35e1ce67acde33a5b5f2b81ff02d2b986ea52f1f6ea211d646a1b"
|
||||||
|
output_filename = "../../../pwnagotchi-32bit.img"
|
||||||
|
qemu_binary = "qemu-arm-static"
|
||||||
|
qemu_args = ["-cpu", "arm1176"]
|
||||||
|
image_arch = "arm"
|
||||||
|
image_mounts = ["/boot/firmware","/"]
|
||||||
|
target_image_size = 19969908736
|
||||||
|
}
|
||||||
|
|
||||||
|
build {
|
||||||
|
name = "Raspberry Pi 32 Pwnagotchi"
|
||||||
|
sources = ["source.arm-image.rpi32-pwnagotchi"]
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/bin/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/usr/bin/bettercap-launcher",
|
||||||
|
"data/32bit/usr/bin/hdmioff",
|
||||||
|
"data/32bit/usr/bin/hdmion",
|
||||||
|
"data/32bit/usr/bin/monstart",
|
||||||
|
"data/32bit/usr/bin/monstop",
|
||||||
|
"data/32bit/usr/bin/pwnagotchi-launcher",
|
||||||
|
"data/32bit/usr/bin/pwnlib",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /usr/bin/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/local/src/pwnagotchi/"
|
||||||
|
source = "../"
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/systemd/system/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/etc/systemd/system/bettercap.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwnagotchi.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
|
source = "data/32bit/etc/update-motd.d/01-motd"
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||||
|
}
|
||||||
|
provisioner "ansible-local" {
|
||||||
|
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||||
|
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||||
|
playbook_file = "raspberrypi32.yml"
|
||||||
|
}
|
||||||
|
}
|
560
builder/raspberrypi32.yml
Normal file
560
builder/raspberrypi32.yml
Normal file
@ -0,0 +1,560 @@
|
|||||||
|
---
|
||||||
|
- hosts:
|
||||||
|
- 127.0.0.1
|
||||||
|
gather_facts: true
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
kernel:
|
||||||
|
min: "6.6"
|
||||||
|
full: "6.6.31+rpt-rpi-v6"
|
||||||
|
pwnagotchi:
|
||||||
|
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||||
|
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
|
||||||
|
services:
|
||||||
|
enable:
|
||||||
|
- bettercap.service
|
||||||
|
- fstrim.timer
|
||||||
|
- pwnagotchi.service
|
||||||
|
- pwngrid-peer.service
|
||||||
|
disable:
|
||||||
|
- apt-daily-upgrade.service
|
||||||
|
- apt-daily-upgrade.timer
|
||||||
|
- apt-daily.service
|
||||||
|
- apt-daily.timer
|
||||||
|
- bluetooth.service
|
||||||
|
- ifup@wlan0.service
|
||||||
|
packages:
|
||||||
|
caplets:
|
||||||
|
source: "https://github.com/jayofelony/caplets.git"
|
||||||
|
branch: "lite" # or master
|
||||||
|
bettercap:
|
||||||
|
source: "https://github.com/jayofelony/bettercap.git"
|
||||||
|
branch: "lite" # or master
|
||||||
|
pwngrid:
|
||||||
|
source: "https://github.com/jayofelony/pwngrid.git"
|
||||||
|
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.7/pwngrid-1.10.7-armhf.zip"
|
||||||
|
torch:
|
||||||
|
wheel: "torch-2.1.0a0+gita8e7c98-cp311-cp311-linux_armv6l.whl"
|
||||||
|
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/py0torch-bookworm-2024-05/torch-2.1.0a0+gita8e7c98-cp311-cp311-linux_armv6l.whl"
|
||||||
|
torchvision:
|
||||||
|
wheel: "torchvision-0.16.0+fbb4cc5-cp311-cp311-linux_armv6l.whl"
|
||||||
|
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/py0torch-bookworm-2024-05/torchvision-0.16.0+fbb4cc5-cp311-cp311-linux_armv6l.whl"
|
||||||
|
apt:
|
||||||
|
downgrade:
|
||||||
|
- libpcap-dev_1.9.1-4_armhf.deb
|
||||||
|
- libpcap0.8-dbg_1.9.1-4_armhf.deb
|
||||||
|
- libpcap0.8-dev_1.9.1-4_armhf.deb
|
||||||
|
- libpcap0.8_1.9.1-4_armhf.deb
|
||||||
|
hold:
|
||||||
|
- firmware-atheros
|
||||||
|
- firmware-brcm80211
|
||||||
|
- firmware-libertas
|
||||||
|
- firmware-misc-nonfree
|
||||||
|
- firmware-realtek
|
||||||
|
- libpcap-dev
|
||||||
|
- libpcap0.8
|
||||||
|
- libpcap0.8-dbg
|
||||||
|
- libpcap0.8-dev
|
||||||
|
remove:
|
||||||
|
- nfs-common
|
||||||
|
- triggerhappy
|
||||||
|
install:
|
||||||
|
- aircrack-ng
|
||||||
|
- autoconf
|
||||||
|
- bison
|
||||||
|
- bluez
|
||||||
|
- bluez-tools
|
||||||
|
- build-essential
|
||||||
|
- curl
|
||||||
|
- dphys-swapfile
|
||||||
|
- fbi
|
||||||
|
- firmware-atheros
|
||||||
|
- firmware-brcm80211
|
||||||
|
- firmware-libertas
|
||||||
|
- firmware-misc-nonfree
|
||||||
|
- firmware-realtek
|
||||||
|
- flex
|
||||||
|
- g++
|
||||||
|
- gawk
|
||||||
|
- gcc-arm-none-eabi
|
||||||
|
- git
|
||||||
|
- libatlas-base-dev
|
||||||
|
- libc6-dev
|
||||||
|
- libcpuinfo-dev
|
||||||
|
- libcurl-ocaml-dev
|
||||||
|
- libdbus-1-dev
|
||||||
|
- libdbus-glib-1-dev
|
||||||
|
- libfl-dev
|
||||||
|
- libgmp3-dev
|
||||||
|
- libnetfilter-queue-dev
|
||||||
|
- libopenblas-dev # https://stackoverflow.com/questions/14570011/explain-why-numpy-should-not-be-imported-from-source-directory
|
||||||
|
- libopenjp2-7
|
||||||
|
- libpcap-dev
|
||||||
|
#- libraspberrypi-bin ## seems to be provided by raspi-utils now
|
||||||
|
- libraspberrypi-dev
|
||||||
|
- libraspberrypi-doc
|
||||||
|
- libraspberrypi0
|
||||||
|
- libsleef-dev
|
||||||
|
- libssl-dev
|
||||||
|
- libssl-ocaml-dev
|
||||||
|
- libtool
|
||||||
|
- libusb-1.0-0-dev
|
||||||
|
- make
|
||||||
|
- ntp
|
||||||
|
- pkg-config
|
||||||
|
- python3-dev
|
||||||
|
- python3-pip
|
||||||
|
- python3-protobuf
|
||||||
|
- python3-setuptools
|
||||||
|
- python3-smbus
|
||||||
|
- qpdf
|
||||||
|
- raspberrypi-kernel-headers
|
||||||
|
- rsync
|
||||||
|
- tcpdump
|
||||||
|
- texinfo
|
||||||
|
- unzip
|
||||||
|
- wget
|
||||||
|
- wl
|
||||||
|
- xxd
|
||||||
|
- zlib1g-dev
|
||||||
|
environment:
|
||||||
|
ARCHFLAGS: "-arch armv6l"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
# First we install packages
|
||||||
|
- name: install packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages.apt.install }}"
|
||||||
|
state: latest
|
||||||
|
update_cache: yes
|
||||||
|
install_recommends: no
|
||||||
|
|
||||||
|
- name: update pip3, setuptools, wheel
|
||||||
|
shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src
|
||||||
|
|
||||||
|
- name: install 32bit torch
|
||||||
|
shell: "python3 -m pip install {{ packages.torch.url }} {{ packages.torchvision.url }} --break-system-packages"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch armv6l"
|
||||||
|
|
||||||
|
# Now we set up /boot/firmware
|
||||||
|
- name: Create pi user
|
||||||
|
copy:
|
||||||
|
dest: /boot/firmware/userconf
|
||||||
|
content: |
|
||||||
|
pi:$5$733Efsksay$SEFUKemv8FaNAu6X4GUfxdSzSDh6PbpOcdtNe5b7Nt0
|
||||||
|
|
||||||
|
- name: enable ssh on boot
|
||||||
|
file:
|
||||||
|
path: /boot/firmware/ssh
|
||||||
|
state: touch
|
||||||
|
|
||||||
|
- name: remove current rc.local
|
||||||
|
file:
|
||||||
|
path: /etc/rc.local
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: change root partition
|
||||||
|
replace:
|
||||||
|
dest: /boot/firmware/cmdline.txt
|
||||||
|
backup: no
|
||||||
|
regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+"
|
||||||
|
replace: "root=/dev/mmcblk0p2"
|
||||||
|
|
||||||
|
- name: configure /boot/firmware/cmdline.txt
|
||||||
|
lineinfile:
|
||||||
|
path: /boot/firmware/cmdline.txt
|
||||||
|
backrefs: True
|
||||||
|
state: present
|
||||||
|
backup: no
|
||||||
|
regexp: '(.*)$'
|
||||||
|
line: '\1 modules-load=dwc2,g_ether'
|
||||||
|
|
||||||
|
- name: setup /boot/firmware/config.txt
|
||||||
|
blockinfile:
|
||||||
|
path: /boot/firmware/config.txt
|
||||||
|
insertafter: EOF
|
||||||
|
block: |
|
||||||
|
dtparam=i2c1=on
|
||||||
|
dtparam=i2c_arm=on
|
||||||
|
dtparam=spi=on
|
||||||
|
gpu_mem=1
|
||||||
|
dtoverlay=dwc2
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
enable_uart=1
|
||||||
|
|
||||||
|
[pi0]
|
||||||
|
dtoverlay=spi0-2cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
- name: change hostname
|
||||||
|
lineinfile:
|
||||||
|
dest: /etc/hostname
|
||||||
|
regexp: '^raspberrypi'
|
||||||
|
line: "{{pwnagotchi.hostname}}"
|
||||||
|
state: present
|
||||||
|
when: lookup('file', '/etc/hostname') == "raspberrypi"
|
||||||
|
register: hostname
|
||||||
|
|
||||||
|
- name: add hostname to /etc/hosts
|
||||||
|
lineinfile:
|
||||||
|
dest: /etc/hosts
|
||||||
|
regexp: '^127\.0\.1\.1[ \t]+raspberrypi'
|
||||||
|
line: "127.0.1.1\t{{pwnagotchi.hostname}}"
|
||||||
|
state: present
|
||||||
|
when: hostname.changed
|
||||||
|
|
||||||
|
# Now we disable sap and a2dp, we don't use them on rpi
|
||||||
|
- name: disable sap plugin for bluetooth.service
|
||||||
|
lineinfile:
|
||||||
|
dest: /lib/systemd/system/bluetooth.service
|
||||||
|
regexp: '^ExecStart=/usr/libexec/bluetooth/bluetoothd$'
|
||||||
|
line: 'ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp'
|
||||||
|
state: present
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
#
|
||||||
|
# libpcap v1.9 - build from source
|
||||||
|
#
|
||||||
|
###########################################
|
||||||
|
|
||||||
|
# check for presence, then it can re-run in later parts if needed
|
||||||
|
# use the "make" built in
|
||||||
|
|
||||||
|
# install libpcap before bettercap and pwngrid, so they use it
|
||||||
|
- name: clone libpcap v1.9 from github
|
||||||
|
git:
|
||||||
|
repo: 'https://github.com/the-tcpdump-group/libpcap.git'
|
||||||
|
dest: /usr/local/src/libpcap
|
||||||
|
version: libpcap-1.9
|
||||||
|
|
||||||
|
- name: build and install libpcap into /usr/local/lib
|
||||||
|
shell: "./configure && make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/libpcap
|
||||||
|
|
||||||
|
- name: remove libpcap build folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/libpcap
|
||||||
|
|
||||||
|
- name: create symlink /usr/local/lib/libpcap.so.1.9.1
|
||||||
|
file:
|
||||||
|
src: /usr/local/lib/libpcap.so.1.9.1
|
||||||
|
dest: /usr/local/lib/libpcap.so.0.8
|
||||||
|
state: link
|
||||||
|
|
||||||
|
# install latest hcxtools
|
||||||
|
- name: clone hcxtools
|
||||||
|
git:
|
||||||
|
repo: https://github.com/ZerBea/hcxtools.git
|
||||||
|
dest: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
- name: install hcxtools
|
||||||
|
shell: "make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
- name: remove hcxtools directory
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
# Installing nexmon
|
||||||
|
- name: clone nexmon repository
|
||||||
|
git:
|
||||||
|
repo: https://github.com/DrSchottky/nexmon.git
|
||||||
|
dest: /usr/local/src/nexmon
|
||||||
|
|
||||||
|
- name: make firmware
|
||||||
|
shell: "source ./setup_env.sh && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch armv6l"
|
||||||
|
|
||||||
|
- name: make firmware patch (bcm43430a1)
|
||||||
|
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch armv6l"
|
||||||
|
|
||||||
|
- name: install new firmware (bcm43430a1)
|
||||||
|
copy:
|
||||||
|
src: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/brcmfmac43430-sdio.bin
|
||||||
|
dest: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||||
|
follow: true
|
||||||
|
|
||||||
|
- name: copy modified driver
|
||||||
|
copy:
|
||||||
|
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||||
|
dest: "/usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch armv6l"
|
||||||
|
|
||||||
|
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
|
||||||
|
copy:
|
||||||
|
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||||
|
dest: /usr/lib/firmware/brcm/brcmfmac43436s-sdio.bin
|
||||||
|
follow: true
|
||||||
|
|
||||||
|
# delete blob files that make nexmon sad
|
||||||
|
- name: Delete the firmware blob files to avoid some nexmon crashing
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: '{{ item }}'
|
||||||
|
loop:
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
|
||||||
|
|
||||||
|
- name: backup original driver
|
||||||
|
command: "mv /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||||
|
|
||||||
|
- name: load brcmfmac drivers
|
||||||
|
command: "/sbin/depmod {{ kernel.full }}"
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
|
||||||
|
# To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation
|
||||||
|
- name: Delete nexmon content & directory
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/nexmon/
|
||||||
|
|
||||||
|
- name: Create custom config directory
|
||||||
|
file:
|
||||||
|
path: /etc/pwnagotchi/conf.d/
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
#- name: clone pwnagotchi repository
|
||||||
|
# git:
|
||||||
|
# repo: https://github.com/jayofelony/pwnagotchi.git
|
||||||
|
# dest: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
|
- name: build pwnagotchi wheel
|
||||||
|
command: "pip3 install . --no-cache-dir --break-system-packages"
|
||||||
|
args:
|
||||||
|
chdir: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
|
- name: create /usr/local/share/pwnagotchi/ folder
|
||||||
|
file:
|
||||||
|
path: /usr/local/share/pwnagotchi/
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: Create custom plugin directory
|
||||||
|
file:
|
||||||
|
path: /usr/local/share/pwnagotchi/custom-plugins/
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: remove pwnagotchi folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
#
|
||||||
|
# pwngrid, bettercap
|
||||||
|
#
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
- name: Install go-1.21
|
||||||
|
unarchive:
|
||||||
|
src: https://go.dev/dl/go1.22.3.linux-armv6l.tar.gz
|
||||||
|
dest: /usr/local
|
||||||
|
remote_src: yes
|
||||||
|
register: golang
|
||||||
|
|
||||||
|
- name: Update .bashrc for go-1.21
|
||||||
|
blockinfile:
|
||||||
|
dest: /etc/profile
|
||||||
|
state: present
|
||||||
|
block: |
|
||||||
|
export GOPATH=$HOME/go
|
||||||
|
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
||||||
|
when: golang.changed
|
||||||
|
|
||||||
|
- name: download pwngrid
|
||||||
|
git:
|
||||||
|
repo: "{{ packages.pwngrid.source }}"
|
||||||
|
dest: /usr/local/src/pwngrid
|
||||||
|
|
||||||
|
- name: install pwngrid
|
||||||
|
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/pwngrid
|
||||||
|
|
||||||
|
- name: remove pwngrid folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/pwngrid
|
||||||
|
|
||||||
|
- name: download bettercap
|
||||||
|
git:
|
||||||
|
repo: "{{ packages.bettercap.source }}"
|
||||||
|
version: "{{ packages.bettercap.branch }} "
|
||||||
|
dest: /usr/local/src/bettercap
|
||||||
|
|
||||||
|
- name: install bettercap 2.32.4
|
||||||
|
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/bettercap
|
||||||
|
|
||||||
|
- name: remove bettercap folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/bettercap
|
||||||
|
|
||||||
|
- name: clone bettercap caplets
|
||||||
|
git:
|
||||||
|
repo: "{{ packages.caplets.source }}"
|
||||||
|
version: "{{ packages.caplets.branch }}"
|
||||||
|
dest: /tmp/caplets
|
||||||
|
register: capletsgit
|
||||||
|
|
||||||
|
- name: install bettercap caplets
|
||||||
|
make:
|
||||||
|
chdir: /tmp/caplets
|
||||||
|
target: install
|
||||||
|
when: capletsgit.changed
|
||||||
|
|
||||||
|
- name: create /etc/pwnagotchi folder
|
||||||
|
file:
|
||||||
|
path: /etc/pwnagotchi
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: check if user configuration exists
|
||||||
|
stat:
|
||||||
|
path: /etc/pwnagotchi/config.toml
|
||||||
|
register: user_config
|
||||||
|
|
||||||
|
- name: create /etc/pwnagotchi/config.toml
|
||||||
|
copy:
|
||||||
|
dest: /etc/pwnagotchi/config.toml
|
||||||
|
content: |
|
||||||
|
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||||
|
# Example:
|
||||||
|
# ui.display.enabled = true
|
||||||
|
# ui.display.type = "waveshare_4"
|
||||||
|
when: not user_config.stat.exists
|
||||||
|
|
||||||
|
- name: Delete motd
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /etc/motd
|
||||||
|
|
||||||
|
- name: Delete motd 10-uname
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /etc/update-motd.d/10-uname
|
||||||
|
|
||||||
|
- name: add firmware packages to hold
|
||||||
|
dpkg_selections:
|
||||||
|
name: "{{ item }}"
|
||||||
|
selection: hold
|
||||||
|
with_items: "{{ packages.apt.hold }}"
|
||||||
|
|
||||||
|
- name: disable unnecessary services
|
||||||
|
systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: stopped
|
||||||
|
enabled: no
|
||||||
|
with_items: "{{ services.disable }}"
|
||||||
|
|
||||||
|
- name: enable services
|
||||||
|
systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
enabled: true
|
||||||
|
state: stopped
|
||||||
|
with_items: "{{ services.enable }}"
|
||||||
|
register: enabled
|
||||||
|
|
||||||
|
- name: make /root readable, becauase that's where all the files are
|
||||||
|
file:
|
||||||
|
path: /root
|
||||||
|
mode: '755'
|
||||||
|
|
||||||
|
- name: fix permissions on /home/pi
|
||||||
|
file:
|
||||||
|
path: /home/pi
|
||||||
|
owner: pi
|
||||||
|
group: pi
|
||||||
|
recurse: true
|
||||||
|
|
||||||
|
- name: remove pre-collected packages zip
|
||||||
|
file:
|
||||||
|
path: /root/go_pkgs.tgz
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: remove /root/go folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /root/go
|
||||||
|
|
||||||
|
- name: remove /usr/local/go folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/go
|
||||||
|
|
||||||
|
- name: remove pip cache
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /root/.cache/pip
|
||||||
|
|
||||||
|
- name: remove ssh keys
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: "{{ item }}"
|
||||||
|
with_fileglob:
|
||||||
|
- "/etc/ssh/ssh_host*_key*"
|
||||||
|
|
||||||
|
- name: regenerate ssh keys
|
||||||
|
shell: "dpkg-reconfigure openssh-server"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
|
||||||
|
# Now we remove packages
|
||||||
|
- name: remove unnecessary apt packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages.apt.remove }}"
|
||||||
|
state: absent
|
||||||
|
purge: yes
|
||||||
|
register: removed
|
||||||
|
|
||||||
|
- name: remove dependencies that are no longer required
|
||||||
|
apt:
|
||||||
|
autoremove: yes
|
||||||
|
when: removed.changed
|
||||||
|
|
||||||
|
- name: install rpi-sys-mods again?
|
||||||
|
apt:
|
||||||
|
state: present
|
||||||
|
name: raspberrypi-sys-mods
|
||||||
|
update_cache: yes
|
||||||
|
install_recommends: no
|
||||||
|
|
||||||
|
- name: clean apt cache
|
||||||
|
apt:
|
||||||
|
autoclean: true
|
||||||
|
when: removed.changed
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: reload systemd services
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
|
when: enabled.changed
|
84
builder/raspberrypi64.json.pkr.hcl
Normal file
84
builder/raspberrypi64.json.pkr.hcl
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
arm-image = {
|
||||||
|
source = "github.com/solo-io/arm-image"
|
||||||
|
version = ">= 0.0.1"
|
||||||
|
}
|
||||||
|
ansible = {
|
||||||
|
source = "github.com/hashicorp/ansible"
|
||||||
|
version = ">= 1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_hostname" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_version" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm-image" "rpi64-pwnagotchi" {
|
||||||
|
image_type = "raspberrypi"
|
||||||
|
iso_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz"
|
||||||
|
iso_checksum = "sha256:43d150e7901583919e4eb1f0fa83fe0363af2d1e9777a5bb707d696d535e2599"
|
||||||
|
output_filename = "../../../pwnagotchi-64bit.img"
|
||||||
|
qemu_binary = "qemu-aarch64-static"
|
||||||
|
target_image_size = 19969908736
|
||||||
|
image_mounts = ["/boot/firmware","/"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# a build block invokes sources and runs provisioning steps on them. The
|
||||||
|
# documentation for build blocks can be found here:
|
||||||
|
# https://www.packer.io/docs/from-1.5/blocks/build
|
||||||
|
build {
|
||||||
|
name = "Raspberry Pi 64 Pwnagotchi"
|
||||||
|
sources = ["source.arm-image.rpi64-pwnagotchi"]
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/bin/"
|
||||||
|
sources = [
|
||||||
|
"data/64bit/usr/bin/bettercap-launcher",
|
||||||
|
"data/64bit/usr/bin/hdmioff",
|
||||||
|
"data/64bit/usr/bin/hdmion",
|
||||||
|
"data/64bit/usr/bin/monstart",
|
||||||
|
"data/64bit/usr/bin/monstop",
|
||||||
|
"data/64bit/usr/bin/pwnagotchi-launcher",
|
||||||
|
"data/64bit/usr/bin/pwnlib",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /usr/bin/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/local/src/pwnagotchi/"
|
||||||
|
source = "../"
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/systemd/system/"
|
||||||
|
sources = [
|
||||||
|
"data/64bit/etc/systemd/system/bettercap.service",
|
||||||
|
"data/64bit/etc/systemd/system/pwnagotchi.service",
|
||||||
|
"data/64bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
|
source = "data/64bit/etc/update-motd.d/01-motd"
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||||
|
}
|
||||||
|
provisioner "ansible-local" {
|
||||||
|
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||||
|
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||||
|
playbook_file = "raspberrypi64.yml"
|
||||||
|
}
|
||||||
|
}
|
631
builder/raspberrypi64.yml
Normal file
631
builder/raspberrypi64.yml
Normal file
@ -0,0 +1,631 @@
|
|||||||
|
---
|
||||||
|
- hosts:
|
||||||
|
- 127.0.0.1
|
||||||
|
gather_facts: true
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
kernel:
|
||||||
|
min: "6.6"
|
||||||
|
full: "6.6.31+rpt-rpi-v8"
|
||||||
|
full_pi5: "6.6.31+rpt-rpi-2712"
|
||||||
|
pwnagotchi:
|
||||||
|
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||||
|
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
|
||||||
|
services:
|
||||||
|
enable:
|
||||||
|
- bettercap.service
|
||||||
|
- fstrim.timer
|
||||||
|
- pwnagotchi.service
|
||||||
|
- pwngrid-peer.service
|
||||||
|
disable:
|
||||||
|
- apt-daily-upgrade.service
|
||||||
|
- apt-daily-upgrade.timer
|
||||||
|
- apt-daily.service
|
||||||
|
- apt-daily.timer
|
||||||
|
- bluetooth.service
|
||||||
|
- ifup@wlan0.service
|
||||||
|
packages:
|
||||||
|
caplets:
|
||||||
|
source: "https://github.com/jayofelony/caplets.git"
|
||||||
|
branch: "lite" # or master
|
||||||
|
bettercap:
|
||||||
|
source: "https://github.com/jayofelony/bettercap.git"
|
||||||
|
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.4/bettercap-2.32.4.zip"
|
||||||
|
branch: "lite" # or master
|
||||||
|
pwngrid:
|
||||||
|
source: "https://github.com/jayofelony/pwngrid.git"
|
||||||
|
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.5/pwngrid-1.10.5-aarch64.zip"
|
||||||
|
apt:
|
||||||
|
downgrade:
|
||||||
|
- libpcap-dev_1.9.1-4_arm64.deb
|
||||||
|
- libpcap0.8-dbg_1.9.1-4_arm64.deb
|
||||||
|
- libpcap0.8-dev_1.9.1-4_arm64.deb
|
||||||
|
- libpcap0.8_1.9.1-4_arm64.deb
|
||||||
|
hold:
|
||||||
|
- firmware-atheros
|
||||||
|
- firmware-brcm80211
|
||||||
|
- firmware-libertas
|
||||||
|
- firmware-misc-nonfree
|
||||||
|
- firmware-realtek
|
||||||
|
- libpcap-dev
|
||||||
|
- libpcap0.8
|
||||||
|
- libpcap0.8-dbg
|
||||||
|
- libpcap0.8-dev
|
||||||
|
remove:
|
||||||
|
- dhpys-swapfile
|
||||||
|
- nfs-common
|
||||||
|
- triggerhappy
|
||||||
|
install:
|
||||||
|
- aircrack-ng
|
||||||
|
- autoconf
|
||||||
|
- bison
|
||||||
|
- bluez
|
||||||
|
- bluez-tools
|
||||||
|
- build-essential
|
||||||
|
- curl
|
||||||
|
- dphys-swapfile
|
||||||
|
- fbi
|
||||||
|
- firmware-atheros
|
||||||
|
- firmware-brcm80211
|
||||||
|
- firmware-libertas
|
||||||
|
- firmware-misc-nonfree
|
||||||
|
- firmware-realtek
|
||||||
|
- flex
|
||||||
|
- g++
|
||||||
|
- gawk
|
||||||
|
- gcc-arm-none-eabi
|
||||||
|
- git
|
||||||
|
- libc6-dev
|
||||||
|
- libcurl-ocaml-dev
|
||||||
|
- libdbus-1-dev
|
||||||
|
- libdbus-glib-1-dev
|
||||||
|
- libfl-dev
|
||||||
|
- libgmp3-dev
|
||||||
|
- libnetfilter-queue-dev
|
||||||
|
- libpcap-dev
|
||||||
|
#- libraspberrypi-bin ## seems to be provided by raspi-utils now
|
||||||
|
- libraspberrypi-dev
|
||||||
|
- libraspberrypi-doc
|
||||||
|
- libraspberrypi0
|
||||||
|
- libssl-dev
|
||||||
|
- libssl-ocaml-dev
|
||||||
|
- libtool
|
||||||
|
- libusb-1.0-0-dev
|
||||||
|
- make
|
||||||
|
- ntp
|
||||||
|
- pkg-config
|
||||||
|
- python3-dev
|
||||||
|
- python3-pip
|
||||||
|
- python3-setuptools
|
||||||
|
- python3-smbus
|
||||||
|
- qpdf
|
||||||
|
- raspberrypi-kernel-headers
|
||||||
|
- raspberrypi-sys-mods
|
||||||
|
- rsync
|
||||||
|
- tcpdump
|
||||||
|
- texinfo
|
||||||
|
- unzip
|
||||||
|
- wget
|
||||||
|
- wl
|
||||||
|
- xxd
|
||||||
|
- zlib1g-dev
|
||||||
|
environment:
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
# First we install packages
|
||||||
|
- name: install packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages.apt.install }}"
|
||||||
|
state: latest
|
||||||
|
update_cache: yes
|
||||||
|
install_recommends: no
|
||||||
|
|
||||||
|
- name: update pip3, setuptools, wheel
|
||||||
|
shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src
|
||||||
|
|
||||||
|
- name: build pwnagotchi wheel
|
||||||
|
command: "pip3 install . --no-cache-dir --break-system-packages"
|
||||||
|
args:
|
||||||
|
chdir: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
|
- name: remove pwnagotchi folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
|
# Now we set up /boot/firmware
|
||||||
|
- name: Create pi user
|
||||||
|
copy:
|
||||||
|
dest: /boot/firmware/userconf
|
||||||
|
content: |
|
||||||
|
pi:$5$733Efsksay$SEFUKemv8FaNAu6X4GUfxdSzSDh6PbpOcdtNe5b7Nt0
|
||||||
|
|
||||||
|
- name: enable ssh on boot
|
||||||
|
file:
|
||||||
|
path: /boot/firmware/ssh
|
||||||
|
state: touch
|
||||||
|
|
||||||
|
- name: remove current rc.local
|
||||||
|
file:
|
||||||
|
path: /etc/rc.local
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: change root partition
|
||||||
|
replace:
|
||||||
|
dest: /boot/firmware/cmdline.txt
|
||||||
|
backup: no
|
||||||
|
regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+"
|
||||||
|
replace: "root=/dev/mmcblk0p2"
|
||||||
|
|
||||||
|
- name: configure /boot/firmware/cmdline.txt
|
||||||
|
lineinfile:
|
||||||
|
path: /boot/firmware/cmdline.txt
|
||||||
|
backrefs: True
|
||||||
|
state: present
|
||||||
|
backup: no
|
||||||
|
regexp: '(.*)$'
|
||||||
|
line: '\1 modules-load=dwc2,g_ether'
|
||||||
|
|
||||||
|
- name: setup /boot/firmware/config.txt
|
||||||
|
blockinfile:
|
||||||
|
path: /boot/firmware/config.txt
|
||||||
|
insertafter: EOF
|
||||||
|
block: |
|
||||||
|
dtparam=i2c1=on
|
||||||
|
dtparam=i2c_arm=on
|
||||||
|
dtparam=spi=on
|
||||||
|
gpu_mem=1
|
||||||
|
dtoverlay=dwc2
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
enable_uart=1
|
||||||
|
|
||||||
|
[pi02w]
|
||||||
|
dtoverlay=spi0-2cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi3]
|
||||||
|
dtoverlay=spi0-2cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi4]
|
||||||
|
dtoverlay=spi0-2cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi5]
|
||||||
|
dtoverlay=spi0-2cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
- name: change hostname
|
||||||
|
lineinfile:
|
||||||
|
dest: /etc/hostname
|
||||||
|
regexp: '^raspberrypi'
|
||||||
|
line: "{{pwnagotchi.hostname}}"
|
||||||
|
state: present
|
||||||
|
when: lookup('file', '/etc/hostname') == "raspberrypi"
|
||||||
|
register: hostname
|
||||||
|
|
||||||
|
- name: add hostname to /etc/hosts
|
||||||
|
lineinfile:
|
||||||
|
dest: /etc/hosts
|
||||||
|
regexp: '^127\.0\.1\.1[ \t]+raspberrypi'
|
||||||
|
line: "127.0.1.1\t{{pwnagotchi.hostname}}"
|
||||||
|
state: present
|
||||||
|
when: hostname.changed
|
||||||
|
|
||||||
|
# Now we disable sap and a2dp, we don't use them on rpi
|
||||||
|
- name: disable sap plugin for bluetooth.service
|
||||||
|
lineinfile:
|
||||||
|
dest: /lib/systemd/system/bluetooth.service
|
||||||
|
regexp: '^ExecStart=/usr/libexec/bluetooth/bluetoothd$'
|
||||||
|
line: 'ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp'
|
||||||
|
state: present
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
#
|
||||||
|
# libpcap v1.9 - build from source
|
||||||
|
#
|
||||||
|
###########################################
|
||||||
|
|
||||||
|
# check for presence, then it can re-run in later parts if needed
|
||||||
|
# use the "make" built in
|
||||||
|
|
||||||
|
# install libpcap before bettercap and pwngrid, so they use it
|
||||||
|
- name: clone libpcap v1.9 from github
|
||||||
|
git:
|
||||||
|
repo: 'https://github.com/the-tcpdump-group/libpcap.git'
|
||||||
|
dest: /usr/local/src/libpcap
|
||||||
|
version: libpcap-1.9
|
||||||
|
|
||||||
|
- name: build and install libpcap into /usr/local/lib
|
||||||
|
shell: "./configure && make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/libpcap
|
||||||
|
|
||||||
|
- name: remove libpcap build folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/libpcap
|
||||||
|
|
||||||
|
- name: create symlink /usr/local/lib/libpcap.so.1.9.1
|
||||||
|
file:
|
||||||
|
src: /usr/local/lib/libpcap.so.1.9.1
|
||||||
|
dest: /usr/local/lib/libpcap.so.0.8
|
||||||
|
state: link
|
||||||
|
|
||||||
|
# install latest hcxtools
|
||||||
|
- name: clone hcxtools
|
||||||
|
git:
|
||||||
|
repo: https://github.com/ZerBea/hcxtools.git
|
||||||
|
dest: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
- name: install hcxtools
|
||||||
|
shell: "make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
- name: remove hcxtools directory
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/hcxtools
|
||||||
|
|
||||||
|
# Installing nexmon
|
||||||
|
- name: clone nexmon repository
|
||||||
|
git:
|
||||||
|
repo: https://github.com/DrSchottky/nexmon.git
|
||||||
|
dest: /usr/local/src/nexmon
|
||||||
|
|
||||||
|
# FIRST WE BUILD DRIVER FOR RPi5
|
||||||
|
- name: make firmware, RPi5
|
||||||
|
shell: "source ./setup_env.sh && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
- name: make firmware patch (bcm43455c0), RPi5
|
||||||
|
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
- name: copy modified driver, RPi5
|
||||||
|
copy:
|
||||||
|
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||||
|
dest: "/usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
- name: backup original driver, RPi5
|
||||||
|
command: "mv /usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||||
|
|
||||||
|
- name: load brcmfmac drivers
|
||||||
|
command: "/sbin/depmod {{ kernel.full_pi5 }}"
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||||
|
|
||||||
|
- name: Delete nexmon content & directory
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/nexmon/
|
||||||
|
|
||||||
|
# NOW WE BUILD DRIVERS FOR RPi4, RPizero2w and RPi3
|
||||||
|
- name: clone nexmon repository
|
||||||
|
git:
|
||||||
|
repo: https://github.com/DrSchottky/nexmon.git
|
||||||
|
dest: /usr/local/src/nexmon
|
||||||
|
|
||||||
|
- name: make firmware, RPi4
|
||||||
|
shell: "source ./setup_env.sh && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
- name: make firmware patch (bcm43455c0), RPi4
|
||||||
|
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
- name: install new firmware (bcm43455c0), RPi4 RPi5
|
||||||
|
copy:
|
||||||
|
src: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/brcmfmac43455-sdio.bin
|
||||||
|
dest: /usr/lib/firmware/brcm/brcmfmac43455-sdio.bin
|
||||||
|
follow: true
|
||||||
|
|
||||||
|
# NOW WE BUILD DRIVERS FOR RPiZero2W, RPi 3
|
||||||
|
- 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/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
- name: install new firmware (bcm43436b0)
|
||||||
|
copy:
|
||||||
|
src: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/brcmfmac43436-sdio.bin
|
||||||
|
dest: /usr/lib/firmware/brcm/brcmfmac43436-sdio.bin
|
||||||
|
follow: true
|
||||||
|
|
||||||
|
- name: make firmware patch (bcm43430a1)
|
||||||
|
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
- name: install new firmware (bcm43430a1)
|
||||||
|
copy:
|
||||||
|
src: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/brcmfmac43430-sdio.bin
|
||||||
|
dest: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||||
|
follow: true
|
||||||
|
|
||||||
|
- name: copy modified driver, RPi4
|
||||||
|
copy:
|
||||||
|
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||||
|
dest: "/usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
ARCHFLAGS: "-arch aarch64"
|
||||||
|
|
||||||
|
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
|
||||||
|
copy:
|
||||||
|
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||||
|
dest: /usr/lib/firmware/brcm/brcmfmac43436s-sdio.bin
|
||||||
|
follow: true
|
||||||
|
|
||||||
|
# delete blob files that make nexmon sad
|
||||||
|
- name: Delete the firmware blob files to avoid some nexmon crashing
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: '{{ item }}'
|
||||||
|
loop:
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430b0-sdio.raspberrypi,model-zero-2-w.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.raspberrypi,model-zero-2-w.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43455-sdio.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/BCM43430A1.raspberrypi,model-zero-2-w.hcd
|
||||||
|
|
||||||
|
- name: backup original driver
|
||||||
|
command: "mv /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||||
|
|
||||||
|
- name: load brcmfmac drivers
|
||||||
|
command: "/sbin/depmod {{ kernel.full }}"
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
|
||||||
|
# To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation
|
||||||
|
- name: Delete nexmon content & directory
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/nexmon/
|
||||||
|
|
||||||
|
- name: Create custom config directory
|
||||||
|
file:
|
||||||
|
path: /etc/pwnagotchi/conf.d/
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: create /usr/local/share/pwnagotchi/ folder
|
||||||
|
file:
|
||||||
|
path: /usr/local/share/pwnagotchi/
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: Create custom plugin directory
|
||||||
|
file:
|
||||||
|
path: /usr/local/share/pwnagotchi/custom-plugins/
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: Install go-1.21
|
||||||
|
unarchive:
|
||||||
|
src: https://go.dev/dl/go1.22.3.linux-arm64.tar.gz
|
||||||
|
dest: /usr/local
|
||||||
|
remote_src: yes
|
||||||
|
register: golang
|
||||||
|
|
||||||
|
- name: Update .bashrc for go-1.21
|
||||||
|
blockinfile:
|
||||||
|
dest: /etc/profile
|
||||||
|
state: present
|
||||||
|
block: |
|
||||||
|
export GOPATH=$HOME/go
|
||||||
|
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
||||||
|
when: golang.changed
|
||||||
|
|
||||||
|
- name: download pwngrid
|
||||||
|
git:
|
||||||
|
repo: "{{ packages.pwngrid.source }}"
|
||||||
|
dest: /usr/local/src/pwngrid
|
||||||
|
|
||||||
|
- name: install pwngrid
|
||||||
|
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/pwngrid
|
||||||
|
|
||||||
|
- name: remove pwngrid folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/pwngrid
|
||||||
|
|
||||||
|
- name: download bettercap
|
||||||
|
git:
|
||||||
|
repo: "{{ packages.bettercap.source }}"
|
||||||
|
version: "{{ packages.bettercap.branch }}"
|
||||||
|
dest: /usr/local/src/bettercap
|
||||||
|
|
||||||
|
- name: install bettercap 2.32.4
|
||||||
|
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/bettercap
|
||||||
|
|
||||||
|
- name: remove bettercap folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/bettercap
|
||||||
|
|
||||||
|
#- name: download and install bettercap
|
||||||
|
# unarchive:
|
||||||
|
# src: "{{ packages.bettercap.url }}"
|
||||||
|
# dest: /usr/local/bin
|
||||||
|
# remote_src: yes
|
||||||
|
# exclude:
|
||||||
|
# - README.md
|
||||||
|
# - LICENSE.md
|
||||||
|
# mode: 0755
|
||||||
|
|
||||||
|
- name: clone bettercap caplets
|
||||||
|
git:
|
||||||
|
repo: "{{ packages.caplets.source }}"
|
||||||
|
version: "{{ packages.caplets.branch }}"
|
||||||
|
dest: /tmp/caplets
|
||||||
|
register: capletsgit
|
||||||
|
|
||||||
|
- name: install bettercap caplets
|
||||||
|
make:
|
||||||
|
chdir: /tmp/caplets
|
||||||
|
target: install
|
||||||
|
when: capletsgit.changed
|
||||||
|
|
||||||
|
- name: create /etc/pwnagotchi folder
|
||||||
|
file:
|
||||||
|
path: /etc/pwnagotchi
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: check if user configuration exists
|
||||||
|
stat:
|
||||||
|
path: /etc/pwnagotchi/config.toml
|
||||||
|
register: user_config
|
||||||
|
|
||||||
|
- name: create /etc/pwnagotchi/config.toml
|
||||||
|
copy:
|
||||||
|
dest: /etc/pwnagotchi/config.toml
|
||||||
|
content: |
|
||||||
|
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||||
|
# Example:
|
||||||
|
# ui.display.enabled = true
|
||||||
|
# ui.display.type = "waveshare_4"
|
||||||
|
when: not user_config.stat.exists
|
||||||
|
|
||||||
|
- name: Delete motd
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /etc/motd
|
||||||
|
|
||||||
|
- name: Delete motd 10-uname
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /etc/update-motd.d/10-uname
|
||||||
|
|
||||||
|
- name: add firmware packages to hold
|
||||||
|
dpkg_selections:
|
||||||
|
name: "{{ item }}"
|
||||||
|
selection: hold
|
||||||
|
with_items: "{{ packages.apt.hold }}"
|
||||||
|
|
||||||
|
- name: disable unnecessary services
|
||||||
|
systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: stopped
|
||||||
|
enabled: no
|
||||||
|
with_items: "{{ services.disable }}"
|
||||||
|
|
||||||
|
- name: enable services
|
||||||
|
systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
enabled: true
|
||||||
|
state: stopped
|
||||||
|
with_items: "{{ services.enable }}"
|
||||||
|
register: enabled
|
||||||
|
|
||||||
|
- name: make /root readable, becauase that's where all the files are
|
||||||
|
file:
|
||||||
|
path: /root
|
||||||
|
mode: '755'
|
||||||
|
|
||||||
|
- name: fix permissions on /home/pi
|
||||||
|
file:
|
||||||
|
path: /home/pi
|
||||||
|
owner: pi
|
||||||
|
group: pi
|
||||||
|
recurse: true
|
||||||
|
|
||||||
|
- name: remove pre-collected packages zip
|
||||||
|
file:
|
||||||
|
path: /root/go_pkgs.tgz
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: remove /root/go folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /root/go
|
||||||
|
|
||||||
|
- name: remove /usr/local/go folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/go
|
||||||
|
|
||||||
|
- name: remove pip cache
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /root/.cache/pip
|
||||||
|
|
||||||
|
- name: remove ssh keys
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: "{{ item }}"
|
||||||
|
with_fileglob:
|
||||||
|
- "/etc/ssh/ssh_host*_key*"
|
||||||
|
|
||||||
|
- name: regenerate ssh keys
|
||||||
|
shell: "dpkg-reconfigure openssh-server"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
|
||||||
|
# Now we remove packages
|
||||||
|
- name: remove unnecessary apt packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages.apt.remove }}"
|
||||||
|
state: absent
|
||||||
|
purge: yes
|
||||||
|
register: removed
|
||||||
|
|
||||||
|
- name: remove dependencies that are no longer required
|
||||||
|
apt:
|
||||||
|
autoremove: yes
|
||||||
|
when: removed.changed
|
||||||
|
|
||||||
|
- name: clean apt cache
|
||||||
|
apt:
|
||||||
|
autoclean: true
|
||||||
|
when: removed.changed
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: reload systemd services
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
|
when: enabled.changed
|
@ -9,6 +9,7 @@ _name = None
|
|||||||
config = None
|
config = None
|
||||||
_cpu_stats = {}
|
_cpu_stats = {}
|
||||||
|
|
||||||
|
|
||||||
def set_name(new_name):
|
def set_name(new_name):
|
||||||
if new_name is None:
|
if new_name is None:
|
||||||
return
|
return
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = '2.9.5.1'
|
__version__ = '2.9.2'
|
||||||
|
@ -16,11 +16,12 @@ from pwnagotchi.automata import Automata
|
|||||||
from pwnagotchi.log import LastSession
|
from pwnagotchi.log import LastSession
|
||||||
from pwnagotchi.bettercap import Client
|
from pwnagotchi.bettercap import Client
|
||||||
from pwnagotchi.mesh.utils import AsyncAdvertiser
|
from pwnagotchi.mesh.utils import AsyncAdvertiser
|
||||||
|
from pwnagotchi.ai.train import AsyncTrainer
|
||||||
|
|
||||||
RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
|
RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
|
||||||
|
|
||||||
|
|
||||||
class Agent(Client, Automata, AsyncAdvertiser):
|
class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||||
def __init__(self, view, config, keypair):
|
def __init__(self, view, config, keypair):
|
||||||
Client.__init__(self,
|
Client.__init__(self,
|
||||||
"127.0.0.1" if "hostname" not in config['bettercap'] else config['bettercap']['hostname'],
|
"127.0.0.1" if "hostname" not in config['bettercap'] else config['bettercap']['hostname'],
|
||||||
@ -30,6 +31,7 @@ class Agent(Client, Automata, AsyncAdvertiser):
|
|||||||
"pwnagotchi" if "password" not in config['bettercap'] else config['bettercap']['password'])
|
"pwnagotchi" if "password" not in config['bettercap'] else config['bettercap']['password'])
|
||||||
Automata.__init__(self, config, view)
|
Automata.__init__(self, config, view)
|
||||||
AsyncAdvertiser.__init__(self, config, view, keypair)
|
AsyncAdvertiser.__init__(self, config, view, keypair)
|
||||||
|
AsyncTrainer.__init__(self, config)
|
||||||
|
|
||||||
self._started_at = time.time()
|
self._started_at = time.time()
|
||||||
self._current_channel = 0
|
self._current_channel = 0
|
||||||
@ -129,6 +131,7 @@ class Agent(Client, Automata, AsyncAdvertiser):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
self.start_ai()
|
||||||
self._wait_bettercap()
|
self._wait_bettercap()
|
||||||
self.setup_events()
|
self.setup_events()
|
||||||
self.set_starting()
|
self.set_starting()
|
||||||
|
74
pwnagotchi/ai/__init__.py
Normal file
74
pwnagotchi/ai/__init__.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
def load(config, agent, epoch, from_disk=True):
|
||||||
|
config = config['ai']
|
||||||
|
if not config['enabled']:
|
||||||
|
logging.info("ai disabled")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
begin = time.time()
|
||||||
|
|
||||||
|
logging.info("[AI] bootstrapping dependencies ...")
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
SB_BACKEND = "stable_baselines3"
|
||||||
|
|
||||||
|
from stable_baselines3 import A2C
|
||||||
|
logging.debug("[AI] A2C imported in %.2fs" % (time.time() - start))
|
||||||
|
|
||||||
|
# remove invalid ai.parameters leftover from tensor_flow, if present
|
||||||
|
for key in [ 'alpha', 'epsilon', 'lr_schedule' ]:
|
||||||
|
if key in config['params']:
|
||||||
|
logging.info("Removing legacy ai parameter %s" % key);
|
||||||
|
del config['params'][key]
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
from stable_baselines3.a2c import MlpPolicy
|
||||||
|
logging.debug("[AI] MlpPolicy imported in %.2fs" % (time.time() - start))
|
||||||
|
SB_A2C_POLICY = MlpPolicy
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
from stable_baselines3.common.vec_env import DummyVecEnv
|
||||||
|
logging.debug("[AI] DummyVecEnv imported in %.2fs" % (time.time() - start))
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
import pwnagotchi.ai.gym as wrappers
|
||||||
|
logging.debug("[AI] gym wrapper imported in %.2fs" % (time.time() - start))
|
||||||
|
|
||||||
|
env = wrappers.Environment(agent, epoch)
|
||||||
|
env = DummyVecEnv([lambda: env])
|
||||||
|
|
||||||
|
logging.info("[AI] creating model ...")
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
a2c = A2C(SB_A2C_POLICY, env, **config['params'])
|
||||||
|
logging.debug("[AI] A2C created in %.2fs" % (time.time() - start))
|
||||||
|
|
||||||
|
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))
|
||||||
|
else:
|
||||||
|
logging.info("[AI] model created:")
|
||||||
|
for key, value in config['params'].items():
|
||||||
|
logging.info(" %s: %s" % (key, value))
|
||||||
|
|
||||||
|
logging.debug("[AI] total loading time is %.2fs" % (time.time() - begin))
|
||||||
|
|
||||||
|
return a2c
|
||||||
|
except Exception as e:
|
||||||
|
logging.info("[AI] Error while starting AI")
|
||||||
|
logging.debug("[AI] error while starting AI (%s)", e)
|
||||||
|
logging.info("[AI] Deleting brain and restarting.")
|
||||||
|
os.system("rm /root/brain.nn && service pwnagotchi restart")
|
||||||
|
|
||||||
|
logging.warning("[AI] AI not loaded!")
|
||||||
|
return False
|
61
pwnagotchi/ai/featurizer.py
Normal file
61
pwnagotchi/ai/featurizer.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import pwnagotchi.mesh.wifi as wifi
|
||||||
|
|
||||||
|
MAX_EPOCH_DURATION = 1024
|
||||||
|
|
||||||
|
|
||||||
|
histogram_size = wifi.NumChannels
|
||||||
|
|
||||||
|
shape = (1,
|
||||||
|
# aps per channel
|
||||||
|
histogram_size +
|
||||||
|
# clients per channel
|
||||||
|
histogram_size +
|
||||||
|
# peers per channel
|
||||||
|
histogram_size +
|
||||||
|
# duration
|
||||||
|
1 +
|
||||||
|
# inactive
|
||||||
|
1 +
|
||||||
|
# active
|
||||||
|
1 +
|
||||||
|
# missed
|
||||||
|
1 +
|
||||||
|
# hops
|
||||||
|
1 +
|
||||||
|
# deauths
|
||||||
|
1 +
|
||||||
|
# assocs
|
||||||
|
1 +
|
||||||
|
# handshakes
|
||||||
|
1)
|
||||||
|
|
||||||
|
|
||||||
|
def featurize(state, step):
|
||||||
|
tot_epochs = step + 1e-10
|
||||||
|
tot_interactions = (state['num_deauths'] + state['num_associations']) + 1e-10
|
||||||
|
return np.concatenate((
|
||||||
|
# aps per channel
|
||||||
|
state['aps_histogram'],
|
||||||
|
# clients per channel
|
||||||
|
state['sta_histogram'],
|
||||||
|
# peers per channel
|
||||||
|
state['peers_histogram'],
|
||||||
|
# duration
|
||||||
|
[np.clip(state['duration_secs'] / MAX_EPOCH_DURATION, 0.0, 1.0)],
|
||||||
|
# inactive
|
||||||
|
[state['inactive_for_epochs'] / tot_epochs],
|
||||||
|
# active
|
||||||
|
[state['active_for_epochs'] / tot_epochs],
|
||||||
|
# missed
|
||||||
|
[state['missed_interactions'] / tot_interactions],
|
||||||
|
# hops
|
||||||
|
[state['num_hops'] / wifi.NumChannels],
|
||||||
|
# deauths
|
||||||
|
[state['num_deauths'] / tot_interactions],
|
||||||
|
# assocs
|
||||||
|
[state['num_associations'] / tot_interactions],
|
||||||
|
# handshakes
|
||||||
|
[state['num_handshakes'] / tot_interactions],
|
||||||
|
))
|
148
pwnagotchi/ai/gym.py
Normal file
148
pwnagotchi/ai/gym.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import logging
|
||||||
|
import gymnasium as gym
|
||||||
|
from gymnasium import spaces
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import pwnagotchi.ai.featurizer as featurizer
|
||||||
|
import pwnagotchi.ai.reward as reward
|
||||||
|
from pwnagotchi.ai.parameter import Parameter
|
||||||
|
|
||||||
|
|
||||||
|
class Environment(gym.Env):
|
||||||
|
render_mode = "human"
|
||||||
|
metadata = {'render_modes': ['human']}
|
||||||
|
params = [
|
||||||
|
Parameter('min_rssi', min_value=-200, max_value=-50),
|
||||||
|
Parameter('ap_ttl', min_value=30, max_value=600),
|
||||||
|
Parameter('sta_ttl', min_value=60, max_value=300),
|
||||||
|
|
||||||
|
Parameter('recon_time', min_value=5, max_value=60),
|
||||||
|
Parameter('max_inactive_scale', min_value=3, max_value=10),
|
||||||
|
Parameter('recon_inactive_multiplier', min_value=1, max_value=3),
|
||||||
|
Parameter('hop_recon_time', min_value=5, max_value=60),
|
||||||
|
Parameter('min_recon_time', min_value=1, max_value=30),
|
||||||
|
Parameter('max_interactions', min_value=1, max_value=25),
|
||||||
|
Parameter('max_misses_for_recon', min_value=3, max_value=10),
|
||||||
|
Parameter('excited_num_epochs', min_value=5, max_value=30),
|
||||||
|
Parameter('bored_num_epochs', min_value=5, max_value=30),
|
||||||
|
Parameter('sad_num_epochs', min_value=5, max_value=30),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, agent, epoch):
|
||||||
|
super(Environment, self).__init__()
|
||||||
|
self._agent = agent
|
||||||
|
self._epoch = epoch
|
||||||
|
self._epoch_num = 0
|
||||||
|
self._last_render = None
|
||||||
|
|
||||||
|
channels = agent.supported_channels()
|
||||||
|
|
||||||
|
Environment.params += [
|
||||||
|
Parameter('_channel_%d' % ch, min_value=0, max_value=1, meta=ch + 1) for ch in
|
||||||
|
range(featurizer.histogram_size) if ch + 1 in channels
|
||||||
|
]
|
||||||
|
|
||||||
|
self.last = {
|
||||||
|
'reward': 0.0,
|
||||||
|
'observation': None,
|
||||||
|
'policy': None,
|
||||||
|
'params': {},
|
||||||
|
'state': None,
|
||||||
|
'state_v': None
|
||||||
|
}
|
||||||
|
|
||||||
|
self.action_space = spaces.multi_discrete.MultiDiscrete([p.space_size() for p in Environment.params if p.trainable])
|
||||||
|
self.observation_space = spaces.Box(low=0, high=1, shape=featurizer.shape, dtype=np.float32)
|
||||||
|
self.reward_range = reward.range
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def policy_size():
|
||||||
|
return len(list(p for p in Environment.params if p.trainable))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def policy_to_params(policy):
|
||||||
|
num = len(policy)
|
||||||
|
params = {}
|
||||||
|
|
||||||
|
assert len(Environment.params) == num
|
||||||
|
|
||||||
|
channels = []
|
||||||
|
|
||||||
|
for i in range(num):
|
||||||
|
param = Environment.params[i]
|
||||||
|
|
||||||
|
if '_channel' not in param.name:
|
||||||
|
params[param.name] = param.to_param_value(policy[i])
|
||||||
|
else:
|
||||||
|
has_chan = param.to_param_value(policy[i])
|
||||||
|
# print("%s policy:%s bool:%s" % (param.name, policy[i], has_chan))
|
||||||
|
chan = param.meta
|
||||||
|
if has_chan:
|
||||||
|
channels.append(chan)
|
||||||
|
|
||||||
|
params['channels'] = channels
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
|
def _next_epoch(self):
|
||||||
|
logging.debug("[ai] waiting for epoch to finish ...")
|
||||||
|
return self._epoch.wait_for_epoch_data()
|
||||||
|
|
||||||
|
def _apply_policy(self, policy):
|
||||||
|
new_params = Environment.policy_to_params(policy)
|
||||||
|
self.last['policy'] = policy
|
||||||
|
self.last['params'] = new_params
|
||||||
|
self._agent.on_ai_policy(new_params)
|
||||||
|
|
||||||
|
def step(self, policy):
|
||||||
|
# create the parameters from the policy and update
|
||||||
|
# them in the algorithm
|
||||||
|
self._apply_policy(policy)
|
||||||
|
self._epoch_num += 1
|
||||||
|
|
||||||
|
# wait for the algorithm to run with the new parameters
|
||||||
|
state = self._next_epoch()
|
||||||
|
|
||||||
|
self.last['reward'] = state['reward']
|
||||||
|
self.last['state'] = state
|
||||||
|
self.last['state_v'] = featurizer.featurize(state, self._epoch_num)
|
||||||
|
|
||||||
|
self._agent.on_ai_step()
|
||||||
|
|
||||||
|
return self.last['state_v'], self.last['reward'], not self._agent.is_training(), {}
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
# logging.info("[ai] resetting environment ...")
|
||||||
|
self._epoch_num = 0
|
||||||
|
state = self._next_epoch()
|
||||||
|
self.last['state'] = state
|
||||||
|
self.last['state_v'] = featurizer.featurize(state, 1)
|
||||||
|
return self.last['state_v']
|
||||||
|
|
||||||
|
def _render_histogram(self, hist):
|
||||||
|
for ch in range(featurizer.histogram_size):
|
||||||
|
if hist[ch]:
|
||||||
|
logging.info(" CH %d: %s" % (ch + 1, hist[ch]))
|
||||||
|
|
||||||
|
def render(self, mode='human', close=False, force=False):
|
||||||
|
# when using a vectorialized environment, render gets called twice
|
||||||
|
# avoid rendering the same data
|
||||||
|
if self._last_render == self._epoch_num:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._agent.is_training() and not force:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._last_render = self._epoch_num
|
||||||
|
|
||||||
|
logging.info("[AI] --- training epoch %d/%d ---" % (self._epoch_num, self._agent.training_epochs()))
|
||||||
|
logging.info("[AI] REWARD: %f" % self.last['reward'])
|
||||||
|
|
||||||
|
logging.debug(
|
||||||
|
"[AI] policy: %s" % ', '.join("%s:%s" % (name, value) for name, value in self.last['params'].items()))
|
||||||
|
|
||||||
|
logging.info("[AI] observation:")
|
||||||
|
for name, value in self.last['state'].items():
|
||||||
|
if 'histogram' in name:
|
||||||
|
logging.info(" %s" % name.replace('_histogram', ''))
|
||||||
|
self._render_histogram(value)
|
30
pwnagotchi/ai/parameter.py
Normal file
30
pwnagotchi/ai/parameter.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from gymnasium import spaces
|
||||||
|
|
||||||
|
|
||||||
|
class Parameter(object):
|
||||||
|
def __init__(self, name, value=0.0, min_value=0, max_value=2, meta=None, trainable=True):
|
||||||
|
self.name = name
|
||||||
|
self.trainable = trainable
|
||||||
|
self.meta = meta
|
||||||
|
self.value = value
|
||||||
|
self.min_value = min_value
|
||||||
|
self.max_value = max_value + 1
|
||||||
|
|
||||||
|
# gymnasium.space.Discrete is within [0, 1, 2, ..., n-1]
|
||||||
|
if self.min_value < 0:
|
||||||
|
self.scale_factor = abs(self.min_value)
|
||||||
|
elif self.min_value > 0:
|
||||||
|
self.scale_factor = -self.min_value
|
||||||
|
else:
|
||||||
|
self.scale_factor = 0
|
||||||
|
|
||||||
|
def space_size(self):
|
||||||
|
return self.max_value + self.scale_factor
|
||||||
|
|
||||||
|
def space(self):
|
||||||
|
return spaces.Discrete(self.max_value + self.scale_factor)
|
||||||
|
|
||||||
|
def to_param_value(self, policy_v):
|
||||||
|
self.value = policy_v - self.scale_factor
|
||||||
|
assert self.min_value <= self.value <= self.max_value
|
||||||
|
return int(self.value)
|
@ -1,28 +1,27 @@
|
|||||||
import pwnagotchi.mesh.wifi as wifi
|
import pwnagotchi.mesh.wifi as wifi
|
||||||
|
|
||||||
range: tuple[float, float] = (-.7, 1.02)
|
range = (-.7, 1.02)
|
||||||
fuck_zero: float = 1e-20
|
fuck_zero = 1e-20
|
||||||
|
|
||||||
|
|
||||||
class RewardFunction(object):
|
class RewardFunction(object):
|
||||||
def __call__(self, epoch_n: float, state: dict[str, float]) -> float:
|
def __call__(self, epoch_n, state):
|
||||||
|
tot_epochs = epoch_n + fuck_zero
|
||||||
|
tot_interactions = max(state['num_deauths'] + state['num_associations'], state['num_handshakes']) + fuck_zero
|
||||||
|
tot_channels = wifi.NumChannels
|
||||||
|
|
||||||
tot_epochs: float = epoch_n + fuck_zero
|
h = state['num_handshakes'] / tot_interactions
|
||||||
tot_interactions: float = max(state['num_deauths'] + state['num_associations'], state['num_handshakes']) + fuck_zero
|
a = .2 * (state['active_for_epochs'] / tot_epochs)
|
||||||
tot_channels: int = wifi.NumChannels
|
c = .1 * (state['num_hops'] / tot_channels)
|
||||||
|
|
||||||
h: float = state['num_handshakes'] / tot_interactions
|
b = -.3 * (state['blind_for_epochs'] / tot_epochs)
|
||||||
a: float = .2 * (state['active_for_epochs'] / tot_epochs)
|
m = -.3 * (state['missed_interactions'] / tot_interactions)
|
||||||
c: float = .1 * (state['num_hops'] / tot_channels)
|
i = -.2 * (state['inactive_for_epochs'] / tot_epochs)
|
||||||
|
|
||||||
b: float = -.3 * (state['blind_for_epochs'] / tot_epochs)
|
|
||||||
m: float = -.3 * (state['missed_interactions'] / tot_interactions)
|
|
||||||
i: float = -.2 * (state['inactive_for_epochs'] / tot_epochs)
|
|
||||||
|
|
||||||
# include emotions if state >= 5 epochs
|
# include emotions if state >= 5 epochs
|
||||||
_sad: float = state['sad_for_epochs'] if state['sad_for_epochs'] >= 5 else 0
|
_sad = state['sad_for_epochs'] if state['sad_for_epochs'] >= 5 else 0
|
||||||
_bored: float = state['bored_for_epochs'] if state['bored_for_epochs'] >= 5 else 0
|
_bored = state['bored_for_epochs'] if state['bored_for_epochs'] >= 5 else 0
|
||||||
s: float = -.2 * (_sad / tot_epochs)
|
s = -.2 * (_sad / tot_epochs)
|
||||||
l: float = -.1 * (_bored / tot_epochs)
|
l = -.1 * (_bored / tot_epochs)
|
||||||
|
|
||||||
return h + a + c + b + i + m + s + l
|
return h + a + c + b + i + m + s + l
|
||||||
|
198
pwnagotchi/ai/train.py
Normal file
198
pwnagotchi/ai/train.py
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
# import _thread
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.plugins as plugins
|
||||||
|
import pwnagotchi.ai as ai
|
||||||
|
|
||||||
|
|
||||||
|
class Stats(object):
|
||||||
|
def __init__(self, path, events_receiver):
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self._receiver = events_receiver
|
||||||
|
|
||||||
|
self.path = path
|
||||||
|
self.born_at = time.time()
|
||||||
|
# total epochs lived (trained + just eval)
|
||||||
|
self.epochs_lived = 0
|
||||||
|
# total training epochs
|
||||||
|
self.epochs_trained = 0
|
||||||
|
|
||||||
|
self.worst_reward = 0.0
|
||||||
|
self.best_reward = 0.0
|
||||||
|
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def on_epoch(self, data, training):
|
||||||
|
best_r = False
|
||||||
|
worst_r = False
|
||||||
|
with self._lock:
|
||||||
|
reward = data['reward']
|
||||||
|
if reward < self.worst_reward:
|
||||||
|
self.worst_reward = reward
|
||||||
|
worst_r = True
|
||||||
|
|
||||||
|
elif reward > self.best_reward:
|
||||||
|
best_r = True
|
||||||
|
self.best_reward = reward
|
||||||
|
|
||||||
|
self.epochs_lived += 1
|
||||||
|
if training:
|
||||||
|
self.epochs_trained += 1
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
if best_r:
|
||||||
|
self._receiver.on_ai_best_reward(reward)
|
||||||
|
elif worst_r:
|
||||||
|
self._receiver.on_ai_worst_reward(reward)
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
with self._lock:
|
||||||
|
if os.path.exists(self.path) and os.path.getsize(self.path) > 0:
|
||||||
|
logging.info("[AI] loading %s" % self.path)
|
||||||
|
with open(self.path, 'rt') as fp:
|
||||||
|
obj = json.load(fp)
|
||||||
|
|
||||||
|
self.born_at = obj['born_at']
|
||||||
|
self.epochs_lived, self.epochs_trained = obj['epochs_lived'], obj['epochs_trained']
|
||||||
|
self.best_reward, self.worst_reward = obj['rewards']['best'], obj['rewards']['worst']
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
with self._lock:
|
||||||
|
logging.info("[AI] saving %s" % self.path)
|
||||||
|
|
||||||
|
data = json.dumps({
|
||||||
|
'born_at': self.born_at,
|
||||||
|
'epochs_lived': self.epochs_lived,
|
||||||
|
'epochs_trained': self.epochs_trained,
|
||||||
|
'rewards': {
|
||||||
|
'best': self.best_reward,
|
||||||
|
'worst': self.worst_reward
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
temp = "%s.tmp" % self.path
|
||||||
|
back = "%s.bak" % self.path
|
||||||
|
with open(temp, 'wt') as fp:
|
||||||
|
fp.write(data)
|
||||||
|
|
||||||
|
if os.path.isfile(self.path):
|
||||||
|
os.replace(self.path, back)
|
||||||
|
os.replace(temp, self.path)
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncTrainer(object):
|
||||||
|
def __init__(self, config):
|
||||||
|
self._config = config
|
||||||
|
self._model = None
|
||||||
|
self._is_training = False
|
||||||
|
self._training_epochs = 0
|
||||||
|
self._nn_path = self._config['ai']['path']
|
||||||
|
self._stats = Stats("%s.json" % os.path.splitext(self._nn_path)[0], self)
|
||||||
|
|
||||||
|
def set_training(self, training, for_epochs=0):
|
||||||
|
self._is_training = training
|
||||||
|
self._training_epochs = for_epochs
|
||||||
|
|
||||||
|
if training:
|
||||||
|
plugins.on('ai_training_start', self, for_epochs)
|
||||||
|
else:
|
||||||
|
plugins.on('ai_training_end', self)
|
||||||
|
|
||||||
|
def is_training(self):
|
||||||
|
return self._is_training
|
||||||
|
|
||||||
|
def training_epochs(self):
|
||||||
|
return self._training_epochs
|
||||||
|
|
||||||
|
def start_ai(self):
|
||||||
|
#_thread.start_new_thread(self._ai_worker, ())
|
||||||
|
threading.Thread(target=self._ai_worker, args=(), name="AI Worker", daemon=True).start()
|
||||||
|
|
||||||
|
def _save_ai(self):
|
||||||
|
logging.info("[AI] saving model to %s ..." % self._nn_path)
|
||||||
|
temp = "%s.tmp" % self._nn_path
|
||||||
|
self._model.save(temp)
|
||||||
|
os.replace(temp, self._nn_path)
|
||||||
|
|
||||||
|
def on_ai_step(self):
|
||||||
|
self._model.env.render()
|
||||||
|
|
||||||
|
if self._is_training:
|
||||||
|
self._save_ai()
|
||||||
|
|
||||||
|
self._stats.on_epoch(self._epoch.data(), self._is_training)
|
||||||
|
|
||||||
|
def on_ai_training_step(self, _locals, _globals):
|
||||||
|
self._model.env.render()
|
||||||
|
plugins.on('ai_training_step', self, _locals, _globals)
|
||||||
|
|
||||||
|
def on_ai_policy(self, new_params):
|
||||||
|
plugins.on('ai_policy', self, new_params)
|
||||||
|
logging.info("[AI] setting new policy:")
|
||||||
|
for name, value in new_params.items():
|
||||||
|
if name in self._config['personality']:
|
||||||
|
curr_value = self._config['personality'][name]
|
||||||
|
if curr_value != value:
|
||||||
|
logging.info("[AI] ! %s: %s -> %s" % (name, curr_value, value))
|
||||||
|
self._config['personality'][name] = value
|
||||||
|
else:
|
||||||
|
logging.error("[AI] param %s not in personality configuration!" % name)
|
||||||
|
|
||||||
|
self.run('set wifi.ap.ttl %d' % self._config['personality']['ap_ttl'])
|
||||||
|
self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl'])
|
||||||
|
self.run('set wifi.rssi.min %d' % self._config['personality']['min_rssi'])
|
||||||
|
|
||||||
|
def on_ai_ready(self):
|
||||||
|
self._view.on_ai_ready()
|
||||||
|
plugins.on('ai_ready', self)
|
||||||
|
|
||||||
|
def on_ai_best_reward(self, r):
|
||||||
|
logging.info("[AI] best reward so far: %s" % r)
|
||||||
|
self._view.on_motivated(r)
|
||||||
|
plugins.on('ai_best_reward', self, r)
|
||||||
|
|
||||||
|
def on_ai_worst_reward(self, r):
|
||||||
|
logging.info("[AI] worst reward so far: %s" % r)
|
||||||
|
self._view.on_demotivated(r)
|
||||||
|
plugins.on('ai_worst_reward', self, r)
|
||||||
|
|
||||||
|
def _ai_worker(self):
|
||||||
|
self._model = ai.load(self._config, self, self._epoch)
|
||||||
|
|
||||||
|
if self._model:
|
||||||
|
self.on_ai_ready()
|
||||||
|
|
||||||
|
epochs_per_episode = self._config['ai']['epochs_per_episode']
|
||||||
|
|
||||||
|
obs = None
|
||||||
|
while True:
|
||||||
|
self._model.env.render()
|
||||||
|
# enter in training mode?
|
||||||
|
if random.random() > self._config['ai']['laziness']:
|
||||||
|
logging.info("[AI] learning for %d epochs ..." % epochs_per_episode)
|
||||||
|
try:
|
||||||
|
self.set_training(True, epochs_per_episode)
|
||||||
|
# back up brain file before starting new training set
|
||||||
|
if os.path.isfile(self._nn_path):
|
||||||
|
back = "%s.bak" % self._nn_path
|
||||||
|
os.replace(self._nn_path, back)
|
||||||
|
self._view.set("mode", " AI")
|
||||||
|
self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step)
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("[AI] error while training (%s)", e)
|
||||||
|
finally:
|
||||||
|
self.set_training(False)
|
||||||
|
obs = self._model.env.reset()
|
||||||
|
# init the first time
|
||||||
|
elif obs is None:
|
||||||
|
obs = self._model.env.reset()
|
||||||
|
|
||||||
|
# run the inference
|
||||||
|
action, _ = self._model.predict(obs)
|
||||||
|
obs, _, _, _ = self._model.env.step(action)
|
16
pwnagotchi/ai/utils.py
Normal file
16
pwnagotchi/ai/utils.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def normalize(v, min_v, max_v):
|
||||||
|
return (v - min_v) / (max_v - min_v)
|
||||||
|
|
||||||
|
|
||||||
|
def as_batches(x, y, batch_size, shuffle=True):
|
||||||
|
x_size = len(x)
|
||||||
|
assert x_size == len(y)
|
||||||
|
|
||||||
|
indices = np.random.permutation(x_size) if shuffle else None
|
||||||
|
|
||||||
|
for offset in range(0, x_size - batch_size + 1, batch_size):
|
||||||
|
excerpt = indices[offset:offset + batch_size] if shuffle else slice(offset, offset + batch_size)
|
||||||
|
yield x[excerpt], y[excerpt]
|
@ -2,7 +2,6 @@ import logging
|
|||||||
|
|
||||||
import pwnagotchi.plugins as plugins
|
import pwnagotchi.plugins as plugins
|
||||||
from pwnagotchi.ai.epoch import Epoch
|
from pwnagotchi.ai.epoch import Epoch
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
# basic mood system
|
# basic mood system
|
||||||
@ -137,6 +136,7 @@ class Automata(object):
|
|||||||
self.set_grateful()
|
self.set_grateful()
|
||||||
|
|
||||||
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
||||||
|
|
||||||
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
||||||
logging.critical("%d epochs without visible access points -> restarting ...", self._epoch.blind_for)
|
logging.critical("%d epochs without visible access points -> restarting ...", self._epoch.blind_for)
|
||||||
self._restart()
|
self._restart()
|
||||||
|
@ -9,25 +9,42 @@ main.whitelist = [
|
|||||||
main.confd = "/etc/pwnagotchi/conf.d/"
|
main.confd = "/etc/pwnagotchi/conf.d/"
|
||||||
main.custom_plugin_repos = [
|
main.custom_plugin_repos = [
|
||||||
"https://github.com/jayofelony/pwnagotchi-torch-plugins/archive/master.zip",
|
"https://github.com/jayofelony/pwnagotchi-torch-plugins/archive/master.zip",
|
||||||
|
"https://github.com/tisboyo/pwnagotchi-pisugar2-plugin/archive/master.zip",
|
||||||
|
"https://github.com/nullm0ose/pwnagotchi-plugin-pisugar3/archive/master.zip",
|
||||||
"https://github.com/Sniffleupagus/pwnagotchi_plugins/archive/master.zip",
|
"https://github.com/Sniffleupagus/pwnagotchi_plugins/archive/master.zip",
|
||||||
"https://github.com/NeonLightning/pwny/archive/master.zip",
|
"https://github.com/NeonLightning/pwny/archive/master.zip",
|
||||||
"https://github.com/marbasec/UPSLite_Plugin_1_3/archive/master.zip",
|
"https://github.com/marbasec/UPSLite_Plugin_1_3/archive/master.zip"
|
||||||
"https://github.com/wpa-2/Pwnagotchi-Plugins/archive/master.zip"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"
|
main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"
|
||||||
|
|
||||||
main.plugins.auto-tune.enabled = true
|
main.plugins.auto-update.enabled = true
|
||||||
|
main.plugins.auto-update.install = true
|
||||||
main.plugins.auto-update.enabled = false
|
|
||||||
main.plugins.auto-update.install = false
|
|
||||||
main.plugins.auto-update.interval = 1
|
main.plugins.auto-update.interval = 1
|
||||||
|
|
||||||
main.plugins.bt-tether.enabled = false
|
main.plugins.bt-tether.enabled = false
|
||||||
main.plugins.bt-tether.phone-name = "" # name as shown on the phone i.e. "Pwnagotchi's Phone"
|
|
||||||
main.plugins.bt-tether.mac = ""
|
main.plugins.bt-tether.devices.android-phone.enabled = false
|
||||||
main.plugins.bt-tether.phone = "" # android or ios
|
main.plugins.bt-tether.devices.android-phone.search_order = 1
|
||||||
main.plugins.bt-tether.ip = "" # 192.168.44.2 android / 172.20.10.2 ios
|
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 = true
|
||||||
|
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 = true
|
||||||
|
main.plugins.bt-tether.devices.ios-phone.priority = 999
|
||||||
|
|
||||||
main.plugins.fix_services.enabled = true
|
main.plugins.fix_services.enabled = true
|
||||||
|
|
||||||
@ -42,8 +59,6 @@ main.plugins.gps.enabled = false
|
|||||||
main.plugins.gps.speed = 19200
|
main.plugins.gps.speed = 19200
|
||||||
main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947"
|
main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947"
|
||||||
|
|
||||||
main.plugins.gps_listener.enabled = false
|
|
||||||
|
|
||||||
main.plugins.grid.enabled = true
|
main.plugins.grid.enabled = true
|
||||||
main.plugins.grid.report = true
|
main.plugins.grid.report = true
|
||||||
|
|
||||||
@ -54,18 +69,18 @@ main.plugins.memtemp.enabled = false
|
|||||||
main.plugins.memtemp.scale = "celsius"
|
main.plugins.memtemp.scale = "celsius"
|
||||||
main.plugins.memtemp.orientation = "horizontal"
|
main.plugins.memtemp.orientation = "horizontal"
|
||||||
|
|
||||||
main.plugins.ohcapi.enabled = false
|
main.plugins.net-pos.enabled = false
|
||||||
main.plugins.ohcapi.api_key = "sk_your_api_key_here"
|
main.plugins.net-pos.api_key = "test"
|
||||||
main.plugins.ohcapi.receive_email = "yes"
|
|
||||||
|
|
||||||
main.plugins.pwndroid.enabled = false
|
main.plugins.onlinehashcrack.enabled = false
|
||||||
main.plugins.pwndroid.display = false # show coords on display
|
main.plugins.onlinehashcrack.email = ""
|
||||||
|
main.plugins.onlinehashcrack.dashboard = ""
|
||||||
|
main.plugins.onlinehashcrack.single_files = false
|
||||||
|
|
||||||
main.plugins.pisugarx.enabled = false
|
main.plugins.pisugar3.enabled = false
|
||||||
main.plugins.pisugarx.rotation = false
|
main.plugins.pisugar3.shutdown = 5
|
||||||
main.plugins.pisugarx.default_display = "percentage"
|
|
||||||
|
|
||||||
main.plugins.session-stats.enabled = false
|
main.plugins.session-stats.enabled = true
|
||||||
main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/"
|
main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/"
|
||||||
|
|
||||||
main.plugins.ups_hat_c.enabled = false
|
main.plugins.ups_hat_c.enabled = false
|
||||||
@ -89,12 +104,11 @@ main.plugins.wpa-sec.enabled = false
|
|||||||
main.plugins.wpa-sec.api_key = ""
|
main.plugins.wpa-sec.api_key = ""
|
||||||
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
|
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
|
||||||
main.plugins.wpa-sec.download_results = false
|
main.plugins.wpa-sec.download_results = false
|
||||||
main.plugins.wpa-sec.show_pwd = false
|
|
||||||
|
|
||||||
main.iface = "wlan0mon"
|
main.iface = "wlan0mon"
|
||||||
main.mon_start_cmd = "/usr/bin/monstart"
|
main.mon_start_cmd = "/usr/bin/monstart"
|
||||||
main.mon_stop_cmd = "/usr/bin/monstop"
|
main.mon_stop_cmd = "/usr/bin/monstop"
|
||||||
main.mon_max_blind_epochs = 5
|
main.mon_max_blind_epochs = 50
|
||||||
main.no_restart = false
|
main.no_restart = false
|
||||||
|
|
||||||
main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log"
|
main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log"
|
||||||
@ -102,6 +116,19 @@ main.log.path-debug = "/etc/pwnagotchi/log/pwnagotchi-debug.log"
|
|||||||
main.log.rotation.enabled = true
|
main.log.rotation.enabled = true
|
||||||
main.log.rotation.size = "10M"
|
main.log.rotation.size = "10M"
|
||||||
|
|
||||||
|
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.verbose = 1
|
||||||
|
|
||||||
personality.advertise = true
|
personality.advertise = true
|
||||||
personality.deauth = true
|
personality.deauth = true
|
||||||
personality.associate = true
|
personality.associate = true
|
||||||
@ -160,9 +187,8 @@ ui.faces.position_y = 34
|
|||||||
|
|
||||||
ui.web.enabled = true
|
ui.web.enabled = true
|
||||||
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
|
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
|
||||||
ui.web.auth = false
|
ui.web.username = "changeme"
|
||||||
ui.web.username = "changeme" # if auth is true
|
ui.web.password = "changeme"
|
||||||
ui.web.password = "changeme" # if auth is true
|
|
||||||
ui.web.origin = ""
|
ui.web.origin = ""
|
||||||
ui.web.port = 8080
|
ui.web.port = 8080
|
||||||
ui.web.on_frame = ""
|
ui.web.on_frame = ""
|
||||||
@ -171,7 +197,7 @@ ui.display.enabled = false
|
|||||||
ui.display.rotation = 180
|
ui.display.rotation = 180
|
||||||
ui.display.type = "waveshare_4"
|
ui.display.type = "waveshare_4"
|
||||||
|
|
||||||
bettercap.handshakes = "/home/pi/handshakes"
|
bettercap.handshakes = "/root/handshakes"
|
||||||
bettercap.silence = [
|
bettercap.silence = [
|
||||||
"ble.device.new",
|
"ble.device.new",
|
||||||
"ble.device.lost",
|
"ble.device.lost",
|
||||||
|
@ -13,8 +13,7 @@ def is_connected():
|
|||||||
try:
|
try:
|
||||||
# check DNS
|
# check DNS
|
||||||
host = 'https://api.opwngrid.xyz/api/v1/uptime'
|
host = 'https://api.opwngrid.xyz/api/v1/uptime'
|
||||||
headers = {'user-agent': f'pwnagotchi/{pwnagotchi.__version__}'}
|
r = requests.get(host, headers=None, timeout=(30.0, 60.0))
|
||||||
r = requests.get(host, headers=headers, timeout=(30.0, 60.0))
|
|
||||||
if r.json().get('isUp'):
|
if r.json().get('isUp'):
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
@ -71,9 +70,10 @@ def update_data(last_session):
|
|||||||
enabled = [name for name, options in pwnagotchi.config['main']['plugins'].items() if
|
enabled = [name for name, options in pwnagotchi.config['main']['plugins'].items() if
|
||||||
'enabled' in options and options['enabled']]
|
'enabled' in options and options['enabled']]
|
||||||
language = pwnagotchi.config['main']['lang']
|
language = pwnagotchi.config['main']['lang']
|
||||||
|
ai = pwnagotchi.config['ai']['enabled']
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'ai': "No AI!",
|
'ai': ai,
|
||||||
'session': {
|
'session': {
|
||||||
'duration': last_session.duration,
|
'duration': last_session.duration,
|
||||||
'epochs': last_session.epochs,
|
'epochs': last_session.epochs,
|
||||||
@ -87,6 +87,7 @@ def update_data(last_session):
|
|||||||
'peers': last_session.peers,
|
'peers': last_session.peers,
|
||||||
},
|
},
|
||||||
'uname': subprocess.getoutput("uname -a"),
|
'uname': subprocess.getoutput("uname -a"),
|
||||||
|
'brain': brain,
|
||||||
'version': pwnagotchi.__version__,
|
'version': pwnagotchi.__version__,
|
||||||
'build': "Pwnagotchi by Jayofelony",
|
'build': "Pwnagotchi by Jayofelony",
|
||||||
'plugins': enabled,
|
'plugins': enabled,
|
||||||
|
@ -50,6 +50,10 @@ class KeyPair(object):
|
|||||||
with open(self.fingerprint_path, 'w+t') as fp:
|
with open(self.fingerprint_path, 'w+t') as fp:
|
||||||
fp.write(self.fingerprint)
|
fp.write(self.fingerprint)
|
||||||
|
|
||||||
|
# no exception, keys loaded correctly.
|
||||||
|
self._view.on_starting()
|
||||||
|
return
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# if we're here, loading the keys broke something ...
|
# if we're here, loading the keys broke something ...
|
||||||
logging.exception("error loading keys, maybe corrupted, deleting and regenerating ...")
|
logging.exception("error loading keys, maybe corrupted, deleting and regenerating ...")
|
||||||
@ -59,9 +63,6 @@ class KeyPair(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# no exception, keys loaded correctly.
|
|
||||||
self._view.on_starting()
|
|
||||||
return
|
|
||||||
def sign(self, message):
|
def sign(self, message):
|
||||||
hasher = SHA256.new(message.encode("ascii"))
|
hasher = SHA256.new(message.encode("ascii"))
|
||||||
signer = PKCS1_PSS.new(self.priv_key, saltLen=16)
|
signer = PKCS1_PSS.new(self.priv_key, saltLen=16)
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 1.5.3\n"
|
"Project-Id-Version: 1.5.3\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: MatthewNunu https://github.com/MatthewNunu\n"
|
"Last-Translator: MatthewNunu https://github.com/MatthewNunu\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@ -28,11 +28,11 @@ msgstr "Nuwe dag, nuwe jag, nuwe pwns!"
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr "Hack die wêreld!"
|
msgstr "Hack die wêreld!"
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr "AI gereed."
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr "Die neurale netwerk is gereed."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr "Genereer wagwoord, moenie afskakel nie ..."
|
msgstr "Genereer wagwoord, moenie afskakel nie ..."
|
||||||
@ -257,9 +257,3 @@ msgstr "minuut"
|
|||||||
|
|
||||||
msgid "second"
|
msgid "second"
|
||||||
msgstr "tweede"
|
msgstr "tweede"
|
||||||
|
|
||||||
#~ msgid "AI ready."
|
|
||||||
#~ msgstr "AI gereed."
|
|
||||||
|
|
||||||
#~ msgid "The neural network is ready."
|
|
||||||
#~ msgstr "Die neurale netwerk is gereed."
|
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
Binary file not shown.
@ -6,7 +6,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Pwnagotchi Belarusian translation v 0.0.1\n"
|
"Project-Id-Version: Pwnagotchi Belarusian translation v 0.0.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||||
"PO-Revision-Date: \n"
|
"PO-Revision-Date: \n"
|
||||||
"Last-Translator: <https://github.com/andreifinski>\n"
|
"Last-Translator: <https://github.com/andreifinski>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@ -33,11 +33,11 @@ msgstr "Новы дзень, новае паляванне, новыя ўзло
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr "Узламай гэту Планету!"
|
msgstr "Узламай гэту Планету!"
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr "A.I. гатовы."
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr "Нейронная сетка гатова."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr "Генерацыя ключоў, не выключайце..."
|
msgstr "Генерацыя ключоў, не выключайце..."
|
||||||
@ -263,12 +263,6 @@ msgstr "хвіліну"
|
|||||||
msgid "second"
|
msgid "second"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#~ msgid "AI ready."
|
|
||||||
#~ msgstr "A.I. гатовы."
|
|
||||||
|
|
||||||
#~ msgid "The neural network is ready."
|
|
||||||
#~ msgstr "Нейронная сетка гатова."
|
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
#~ msgid "Unit {name} is nearby! {name}"
|
#~ msgid "Unit {name} is nearby! {name}"
|
||||||
#~ msgstr "Мэта {name} побач!"
|
#~ msgstr "Мэта {name} побач!"
|
||||||
|
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 0.0.1\n"
|
"Project-Id-Version: 0.0.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||||
"PO-Revision-Date: 2019-10-23 20:56+0200\n"
|
"PO-Revision-Date: 2019-10-23 20:56+0200\n"
|
||||||
"Last-Translator: Georgi Koemdzhiev <https://github.com/georgikoemdzhiev>\n"
|
"Last-Translator: Georgi Koemdzhiev <https://github.com/georgikoemdzhiev>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@ -28,11 +28,11 @@ msgstr "Нов ден, нов лов, нови pwns!"
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr "Хакни планетата!"
|
msgstr "Хакни планетата!"
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr "AI готов."
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr "Невронната мрежа е готова."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr "Генериране на ключове, не изключвай ..."
|
msgstr "Генериране на ключове, не изключвай ..."
|
||||||
@ -258,9 +258,3 @@ msgstr "минута"
|
|||||||
|
|
||||||
msgid "second"
|
msgid "second"
|
||||||
msgstr "секунда"
|
msgstr "секунда"
|
||||||
|
|
||||||
#~ msgid "AI ready."
|
|
||||||
#~ msgstr "AI готов."
|
|
||||||
|
|
||||||
#~ msgid "The neural network is ready."
|
|
||||||
#~ msgstr "Невронната мрежа е готова."
|
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 0.0.1\n"
|
"Project-Id-Version: 0.0.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: Czechball <czechball@users.noreply.github.com>\n"
|
"Last-Translator: Czechball <czechball@users.noreply.github.com>\n"
|
||||||
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
|
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
|
||||||
@ -28,11 +28,11 @@ msgstr "Nový den, nový lov, nové úlovky!"
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr "Hackni celou planetu!"
|
msgstr "Hackni celou planetu!"
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr "AI připraveno."
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr "Neuronová síť je připravena."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr "Generování klíčů, nevypínej mě..."
|
msgstr "Generování klíčů, nevypínej mě..."
|
||||||
@ -257,9 +257,3 @@ msgstr "minuta"
|
|||||||
|
|
||||||
msgid "second"
|
msgid "second"
|
||||||
msgstr "sekunda"
|
msgstr "sekunda"
|
||||||
|
|
||||||
#~ msgid "AI ready."
|
|
||||||
#~ msgstr "AI připraveno."
|
|
||||||
|
|
||||||
#~ msgid "The neural network is ready."
|
|
||||||
#~ msgstr "Neuronová síť je připravena."
|
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 0.0.1\n"
|
"Project-Id-Version: 0.0.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||||
"PO-Revision-Date: 2020-01-18 21:56+ZONE\n"
|
"PO-Revision-Date: 2020-01-18 21:56+ZONE\n"
|
||||||
"Last-Translator: Dennis Kjær Jensen <signout@signout.dk>\n"
|
"Last-Translator: Dennis Kjær Jensen <signout@signout.dk>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,11 +28,11 @@ msgstr "Ny dag, ny jagt, nye pwns!"
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr "Hack planeten!"
|
msgstr "Hack planeten!"
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr "AI klar."
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr "Det neurale netværk er klart."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr "Genererer nøgler, sluk ikke ..."
|
msgstr "Genererer nøgler, sluk ikke ..."
|
||||||
@ -257,9 +257,3 @@ msgstr "minut"
|
|||||||
|
|
||||||
msgid "second"
|
msgid "second"
|
||||||
msgstr "sekund"
|
msgstr "sekund"
|
||||||
|
|
||||||
#~ msgid "AI ready."
|
|
||||||
#~ msgstr "AI klar."
|
|
||||||
|
|
||||||
#~ msgid "The neural network is ready."
|
|
||||||
#~ msgstr "Det neurale netværk er klart."
|
|
||||||
|
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 0.0.1\n"
|
"Project-Id-Version: 0.0.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||||
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
|
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
|
||||||
"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
|
"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
|
||||||
"Language-Team: DE <33197631+dadav@users.noreply.github.com>\n"
|
"Language-Team: DE <33197631+dadav@users.noreply.github.com>\n"
|
||||||
@ -28,11 +28,11 @@ msgstr "Neuer Tag, neue Jagd, neue Pwns!"
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr "Hack den Planeten!"
|
msgstr "Hack den Planeten!"
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr "KI bereit."
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr "Das neurale Netz ist bereit."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr "Generiere Schlüssel, nicht ausschalten..."
|
msgstr "Generiere Schlüssel, nicht ausschalten..."
|
||||||
@ -257,9 +257,3 @@ msgstr "Minute"
|
|||||||
|
|
||||||
msgid "second"
|
msgid "second"
|
||||||
msgstr "Sekunde"
|
msgstr "Sekunde"
|
||||||
|
|
||||||
#~ msgid "AI ready."
|
|
||||||
#~ msgstr "KI bereit."
|
|
||||||
|
|
||||||
#~ msgid "The neural network is ready."
|
|
||||||
#~ msgstr "Das neurale Netz ist bereit."
|
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -28,10 +29,10 @@ msgstr ""
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
|
Binary file not shown.
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 0.0.1\n"
|
"Project-Id-Version: 0.0.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
|
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||||
"PO-Revision-Date: 2019-10-03 08:00+0000\n"
|
"PO-Revision-Date: 2019-10-03 08:00+0000\n"
|
||||||
"Last-Translator: Periklis Fregkos <fregkos@gmail.com>\n"
|
"Last-Translator: Periklis Fregkos <fregkos@gmail.com>\n"
|
||||||
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
|
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
|
||||||
@ -29,11 +29,11 @@ msgstr "Νέα μέρα, νέο κυνήγι, νέα pwns!"
|
|||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr "Hackαρε τον πλανήτη!"
|
msgstr "Hackαρε τον πλανήτη!"
|
||||||
|
|
||||||
msgid "No more mister Wi-Fi!!"
|
msgid "AI ready."
|
||||||
msgstr ""
|
msgstr "ΤΝ έτοιμη."
|
||||||
|
|
||||||
msgid "Pretty fly 4 a Wi-Fi!"
|
msgid "The neural network is ready."
|
||||||
msgstr ""
|
msgstr "Το νευρωνικό δίκτυο είναι έτοιμο."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -258,9 +258,3 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "second"
|
msgid "second"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#~ msgid "AI ready."
|
|
||||||
#~ msgstr "ΤΝ έτοιμη."
|
|
||||||
|
|
||||||
#~ msgid "The neural network is ready."
|
|
||||||
#~ msgstr "Το νευρωνικό δίκτυο είναι έτοιμο."
|
|
||||||
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user