Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Pwnagotchi image buildable #1120

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

llamasoft
Copy link

@llamasoft llamasoft commented Oct 4, 2022

Fixes #1115 and #1073

Description

This PR fixes the many small things (and some big things) required to get the pwnagotchi image buildable again.

⚠️ This PR is best reviewed one commit at a time.

I tried to keep each commit's changes as small as possible while also including a commit message that fully explains the reasoning or requirement for the changes. The full diff may appear large, but the individual commits tell a story that makes the final result easier to understand.

Also, there are a few intermediate changes that are made redundant by later commits. I did this primarily to make reviewing the commits easier but also so you don't have to accept the whole PR if you don't want to. If you disagree with any of the commits or feel that the changes go too far, you can decide which commits to keep and discard everything after it.

Summary of changes:

(See individual commit messages for a more detailed explanation.)

  • 93b6104 to 4748cb6: The absolute minimum amount of changes to get the image to build (similar to Add support for Pi Zero 2W #1108).
    The image boots but is unstable. The build process is still likely to break at any time.
  • 6634015 to f910f04: Fixes image instability caused by the previous set of commits.
    • 6634015: (Optional) Ensures that the correct CPU type is emulated during the build process.
      This ensures that all packages from apt and pip support armv6 and removes the need to handle opencv and tensorflow separately.
    • e88301e and f910f04: Fixes issues related to the updated base image.
  • de8559a to 3d28df6: (Optional) Fixes pwnagotchi's python packaging issues.
    • 3e0fdc4 to f17f784: A sustainable (and hopefully permanent) fix to requirements.txt version pinning using pip-compile.
      This gives pwnagotchi a requirements.txt file that pins everything to the exact versions without having to worry about indirect dependencies causing build issues at a later date.
      The inclusion of a requirements.in file ensures that a working requirements.txt file can be built even if the base image or python version is updated.
    • 3d28df6: Installs pwnagotchi's python and system files from the local branch.
      Previously, the build process would install system files from the local builder/data directory but overwrite them when installing pwnagotchi's python files from the remote master branch.
      This made development for this PR a constant struggle. Each build attempt required me to push changes to my forked repo, update the URLs in the build scripts, build the image, then revert the changes.
      This should also help if you ever wanted to implement automated image builds 😉😉.

Motivation and Context

I wanted to build my own pwnagotchi but could only obtain a Waveshare v3 display. I saw that support for the Waveshare v3 was added to the repo but no image had been released since then. I tried to build the image myself but quickly ran into issues. While using the available image and copying the updated files would be the easy approach, I felt that getting pwnagotchi images buildable again would be the best for everyone.

How Has This Been Tested?

I've built the image multiple times and use it on my personal RPi0W pwnagotchi. One commenter merged the commits into their test branch and said it was working for them as well.

I don't have a way to confirm that this adds RPi0W2 support, some of the changes in this PR are similar to those in #1108.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

This PR is focuses primarily on bug fixes.
There are a few incidental new features (like being able to install pwnagotchi from an sdist) but they are the result of fixes to the build process and should not affect the functionality of the pwnagotchi application itself.

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I've read the CONTRIBUTION guide
  • I have signed-off my commits with git commit -s

I was not able to find the documentation source in order to update it. 🤐

The only documentation update is that the image build prerequisites no longer require go or kpartx:

  • go is no longer required because Packer and the ARM image plugin are downloaded as binary releases.
  • kpartx is no longer required because Packer now uses losetup as of v0.2.2.

Fixes:
- Updates packer version
- Updates packer-plugin-arm-image name
- Downloads packer plugin via packer CLI instead of building from source
- Supports image builds on more machines by downloading the correct
  packer release for the current build machine's CPU type
- Dependencies added to 'image' target to ensure that packer is installed
  and that the image is only rebuilt when the input files are updated
- Prevents Ansible from changing build machine's hostname

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
Raspbian has been deprecated in favor of RaspiOS.
As a consequence, the Raspbian repo has changed from 'stable' to 'oldstable'
which causes the apt-get update command to fail.  This is the minimal change
required to get the outdated image to build correctly.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
The PGP signature file for the re4son-kernel repo expired on August 29, 2022.
The re-signed PGP key is available from the MIT PGP keyserver.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
flask, Jinja2, Werkzeug, and itsdangerous are all from Pallets Projects
who seemingly refuse to use PEP-440's ~= "compatible release" clause.
Instead, all of their project dependencies are ">=" which eventually
causes things to break.
Their canned response is to recommend always staying on the latest version
and to use something like pip-tools to pin dependencies.
pallets/markupsafe#282 (comment)

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
Raspbian has been renamed to Raspberry Pi OS.
RasPiOS on buster is considered legacy but images are still released for it.
A few packages have to be uninstalled (from the main repo) and
reinstalled (from the re4son-kernel repo) to fix dependency issues.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
The default ARM CPU emulated by qemu is armv7l.  While this is fine for most packages,
it leads to the wrong opencv and tensorflow being installed.  By emulating the correct
CPU, we're guaranteed to end up with the correct binaries.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
If the monitor interface is created before the wlan0 interface is up,
the resulting mon0 interface will be in a permanently broken state.
Since the "ifup@wlan0.service" is disabled and getting timing guarantees
from systemd is a challenge, we manually up the interface.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
An interface in static mode is required to have an address defined,
otherwise ifup will throw an error and fail to bring the interface up.
The Debian wiki provided the pre-up/post-down solution:
https://wiki.debian.org/NetworkConfiguration#Bringing_up_an_interface_without_an_IP_address

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
The installation of system files has been moved into the 'install' command
instead of running unconditionally.  This makes it possible to create a source code
distribution of that doesn't immediately clobber the build machine's system files.
This will also allow the image build to be simplified as it will only need to copy the
sdist archive instead of multiple directories worth of files.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
The idea here is that the requirements.in file should be the loosest allowed
requirements for the project itself, then it should be pip-compile'd into
a concrete requirements.txt that has known working values.  This prevents
unnecessarily pinning packages to specific releases which makes it easier
to keep package versions up-to-date going forward.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
@llamasoft
Copy link
Author

Bah, I just spotted an issue with this build. Going to be pushing an update in a bit.

Firstly, all requirements were sorted.  Then, any dependencies not directly
used by pwnagotchi were removed (e.g. gast isn't used directly, but it will
eventually be installed as an indirect dependency of tensorflow).
Next, every single dependency was researched and documented to determine how
it's used in addition to what versions it can be safely upgraded to.
Most dependencies have been updated to use the PEP-440 "compatible release" feature.
Lastly, the --extra-index and --prefer-binary options have been added.
As a result of some unfortunate publication issues (e.g. grpcio v1.46.X),
some non-yanked libraries simply cannot be built from source.
The --prefer-binary option allows pip to prefer an older library version if
it has a binary wheel available.  This also results in faster builds as
fewer requirements actually need to be compiled from source.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
In theory, this file shouldn't need to be regenerated very often.
One thing of note is that pip-compile strongly recommends that it be run
on the target system in order to ensure it selects the correct versions.
This is because pip-compile doesn't (yet) have a way to specify the
Python version, platform, and machine type during compilation.
The setup.py file was also tweaked to ignore any --options in the
requirements.txt file.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
Despite the fact that a headless version of opencv exists,
stable-baselines and gym both request the regular opencv which
has a dependency on libgtk3 despite the fact that they don't use
any of the GUI functionality.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
Installing pwnagotchi using the local sdist has a few key benefits:
- We're no longer installing all of the data files twice,
  once in packer and once in the setup.py script.
- The image is built using the code in the current local repo,
  not whatever code is currently pushed to the remote master branch.
  This makes it much easier to create and test custom images.
- Installs pwnagotchi using pip, just like the auto-update plugin does.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
As the added comments describe, the options available to us from Ansible's
apt module don't actually clean the apt cache.
Manually running apt-get clean recovers around 400MB of space.

Signed-off-by: llamasoft <llamasoft@rm-rf.email>
@llamasoft
Copy link
Author

llamasoft commented Oct 8, 2022

There we go, all fixed up and ready for review!

Changes since the last push:

  • Downgrade Tensorflow to <1.14 due to breaking API changes in preparation of Tensorflow 2.X
  • Add libgtk-3-0 apt package for opencv.
  • Fix pip install command so that the pwnagotchi executable ends up in the correct place.

@llamasoft
Copy link
Author

Somewhat related, but my PR for packer-plugin-arm-image was merged and included in their latest release. 🎉

What that means for this PR: because the Makefile downloads the correct build of packer and packer-plugin-arm-image for the current CPU architecture, it's now possible to build a Pwnagotchi image from a Raspberry Pi! (It takes around 90 minutes on my RasPi 4, but it works!)

@acracer2008
Copy link

I just wanted to say thanks for this. I was able to get a buildable image after making the changes. After that the only issues I had where from python not having the correct libraries installed and it being my first time building an image on Linux

@user171
Copy link

user171 commented Jan 2, 2023

Thanks for keeping this project alive. It looks abandoned to me.
I tried to build your fork on Arch Linux with all dependencies installed but got this error:

==> pwnagotchi: E: Repository 'http://raspbian.raspberrypi.org/raspbian buster InRelease' changed its 'Suite' value from 'stable' to 'oldstable'
==> pwnagotchi: E: Repository 'http://archive.raspberrypi.org/debian buster InRelease' changed its 'Suite' value from 'testing' to 'oldstable'
==> pwnagotchi: deregistering packer-plugin-arm-image-3521742004 with binfmt_misc
==> pwnagotchi: fuser -k /tmp/armimg-457800909
Build 'pwnagotchi' errored after 37 seconds 827 milliseconds: Script exited with non-zero exit status: 100. Allowed exit codes are: [0]

Did I miss something?

EDIT:

yes, I think I missed the switch to the branch build-fix.

RE-EDIT:
Yeah! I could successfully build your image and it works great! Thanks a lot for your work. You should be the maintainer.

@D337z
Copy link

D337z commented Apr 15, 2023

One of the biggest issues that I'm seeing with your code is that it isn't future proof. You're specifying a version of the programs to download instead of having it always try to build with the most recent versions of the programs. This means that anyone building this three years from now will be using outdated software to do so. Personally, I would suggest always downloading the latest version when possible (which doesn't include in apt or curl since they're outdated as well).

@user171
Copy link

user171 commented Apr 15, 2023

@D337z you say :

One of the biggest issues that I'm seeing with your code is that it isn't future proof.

No code is future proof. See https://en.wikipedia.org/wiki/Software_rot.

You're specifying a version of the programs to download instead of having it always try to build with the most recent versions of the programs. This means that anyone building this three years from now will be using outdated software to do so.

Pinning versions is actually good practice. Not updating code for three years would be the problem in your description.
See https://cloud.google.com/blog/topics/developers-practitioners/best-practices-dependency-management.

Copy link

@D337z D337z left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It takes a very good approach to fixing many of the bugs. I'm not sure about the ld.so.prefect method, but it seems to work.

@llamasoft
Copy link
Author

You're specifying a version of the programs to download instead of having it always try to build with the most recent versions of the programs.

This was a deliberate decision, although I tried to keep it as flexible as possible.

Unfortunately, many of the Python packages that pwnagotchi depends on either A) don't follow semantic versioning or B) are still in their 0.X release cycle. The best compromise I was able to come up with is using PEP-440 "compatible release" syntax and using pip-compile to get point-in-time values that are guaranteed to work. I documented a portion of this in the commit messages: 1, 2.

As for system packages, most of them are being installed at the latest version. The system image itself can't default to the latest version because there's no guarantee that its supported by Kali-Pi. Even a seemingly benign system image update within the same major release ended up requiring some manual fixing.

I'm not sure about the ld.so.preload method, but it seems to work.

Yeah... it's a bit hackish, but unfortunately it (or something like it) is required because of how packer works. Once packer mounts the image and chroots into it, the ld.so.prefetch is going to be seen as if it's the host system's ld.so.preload. This can result it in loading libraries of the mounted image's CPU architecture before qemu has a chance to kick in.

The code shouldn't be putting packer into the /usr/bin of the system and should, instead, reference it directly after compiled so that any newer binaries won't be replaced.

It's not doing that any more. It now downloads a static build of packer instead of building it from source (so it no longer needs golang installed on the host) and stores it in /tmp/pwnagotchi/packer. The packer install command downloads the arm image plugin and stores it under the root user's $HOME/.packer/ directory. While this permanent modification is unfortunate, the plugin binaries are versioned and won't conflict with any other version of the plugin that the user has installed.
If at a later date the packer config is updated to HCL, plugin installation and management can be handled directly via packer configs.

As an added bonus, attempting to compile the setup.py script doesn't result it in automatically clobbering the build system's files.

Like I said, it's not perfect but I tried to A) keep the changes as minimal as possible and B) not change the overall architecture of the project.

@D337z
Copy link

D337z commented Apr 15, 2023

I am wondering, how are the files being put into the image via packer? All I'm seeing in the json file is that they're being tossing into the src directory instead of the usr/bin of the image. I ran into that small snag. Also, the {{user pwnagotchi_version}} line does not work properly.

@llamasoft
Copy link
Author

llamasoft commented Apr 15, 2023 via email

@pepperbob
Copy link

So after numpy problems with the latest official release (see all the numpy issues, AI not starting) I came by this PR, ran make, flashed the image on a RPI0W and it still works like a charm! Kudos @llamasoft

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] pwnagotchi image can't be built
5 participants