Merge pull request #1594 from onionshare/ci-builds

Builds in CI
This commit is contained in:
Micah Lee 2022-07-04 15:03:08 -04:00 committed by GitHub
commit b7ec0cd902
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1537 additions and 1123 deletions

View file

@ -1,60 +1,437 @@
version: 2
version: 2.1
orbs:
win: circleci/windows@4.0.0
workflows:
version: 2
test:
ci:
jobs:
- test-cli
- test-gui
- build-win64:
requires:
- test-cli
- test-gui
- build-win32:
requires:
- test-cli
- test-gui
- build-macos:
requires:
- test-cli
- test-gui
jobs:
test-cli:
docker:
- image: cimg/python:3.9
working_directory: ~/repo
steps:
- checkout
- run:
name: Install dependencies
command: |
sudo apt-get update
sudo apt-get -y install tor obfs4proxy
pip install poetry
cd ~/repo/cli
- restore_cache:
key: test-cli-poetry-deps-{{ .Environment.CACHE_VERSION }}-{{ checksum "~/project/cli/poetry.lock" }}
- run:
name: Install poetry dependencies
command: |
cd ~/project/cli
poetry install
- save_cache:
key: test-cli-poetry-deps-{{ .Environment.CACHE_VERSION }}-{{ checksum "~/project/cli/poetry.lock" }}
paths:
- /home/circleci/.cache/pypoetry/virtualenvs
- run:
name: Run tests
command: |
cd ~/repo/cli
cd ~/project/cli
poetry run pytest -v ./tests
poetry run onionshare-cli --local-only ./tests --auto-stop-timer 2
poetry run onionshare-cli --local-only --receive --auto-stop-timer 2
poetry run onionshare-cli --local-only --website ../docs --auto-stop-timer 2
poetry run onionshare-cli --local-only --chat --auto-stop-timer 2
test-gui:
docker:
- image: cimg/python:3.9
working_directory: ~/repo
steps:
- checkout
- run:
name: Install dependencies
command: |
sudo apt-get update
sudo apt-get install -y tor obfs4proxy gcc python3-dev python3-pyside2.qtcore python3-pyside2.qtwidgets python3-pyside2.qtgui
sudo apt-get install -y xvfb x11-utils libxkbcommon-x11-0 libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-render-util0 libxcb-icccm4 libxcb-keysyms1 libxcb-image0
cd ~/repo/desktop
- restore_cache:
key: test-desktop-poetry-deps-{{ checksum "~/project/desktop/poetry.lock" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Install poetry dependencies
command: |
cd ~/project/desktop
poetry install
- save_cache:
key: test-desktop-poetry-deps-{{ checksum "~/project/desktop/poetry.lock" }}-{{ .Environment.CACHE_VERSION }}
paths:
- /home/circleci/.cache/pypoetry/virtualenvs
- run:
name: Run tests
command: |
cd ~/repo/desktop
cd ~/project/desktop
QT_DEBUG_PLUGINS=1 xvfb-run poetry run pytest -v ./tests/test_gui_*.py
build-win64:
executor:
name: win/default
shell: powershell.exe
steps:
- checkout
- run:
name: Install Python 3.9.13 (64-bit)
command: |
cd ~\Downloads
Invoke-WebRequest -Uri https://www.python.org/ftp/python/3.9.13/python-3.9.13-amd64.exe -OutFile python-3.9.13-amd64.exe
.\python-3.9.13-amd64.exe /quiet InstallAllUsers=1 TargetDir=C:\Python39
while($true) {
if ((Test-Path -Path C:\Python39\python.exe) -eq $True) {
Write-Output "Python is installed"
break
} else {
Write-Output "Waiting for Python to finish installing ..."
Start-Sleep -Seconds 2
}
}
- run:
name: Install poetry
command: C:\Python39\python -m pip install poetry
# - restore_cache:
# key: build-win64-desktop-poetry-deps-{{ checksum "~/project/desktop/poetry.lock" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Install poetry dependencies
command: |
cd C:\Users\circleci\project\desktop
C:\Python39\scripts\poetry install
# - save_cache:
# key: build-win64-desktop-poetry-deps-{{ checksum "~/project/desktop/poetry.lock" }}-{{ .Environment.CACHE_VERSION }}
# paths:
# - C:\Users\circleci\AppData\Local\pypoetry\Cache\virtualenvs
- restore_cache:
key: get-tor-win64-{{ checksum "desktop/scripts/get-tor.py" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Get tor binaries from Tor Browser (64-bit)
command: |
cd desktop
C:\Python39\Scripts\poetry run python .\scripts\get-tor.py win64
- save_cache:
key: get-tor-win64-{{ checksum "desktop/scripts/get-tor.py" }}-{{ .Environment.CACHE_VERSION }}
paths:
- C:\Users\circleci\project\desktop\build\tor
- restore_cache:
key: build-win64-obfs4proxy-{{ checksum "~/project/desktop/scripts/build-pt-obfs4proxy.ps1" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Build obfs4proxy
command: |
if ((Test-Path -Path 'C:\Users\circleci\project\desktop\onionshare\resources\tor\obfs4proxy.exe') -eq $True) {
Write-Output "obfs4proxy already built"
} else {
cd C:\Users\circleci\project\desktop
.\scripts\build-pt-obfs4proxy.ps1
}
- save_cache:
key: build-win64-obfs4proxy-{{ checksum "~/project/desktop/scripts/build-pt-obfs4proxy.ps1" }}-{{ .Environment.CACHE_VERSION }}
paths:
- C:\Users\circleci\project\desktop\onionshare\resources\tor\obfs4proxy.exe
- restore_cache:
key: build-win64-snowflake-{{ checksum "~/project/desktop/scripts/build-pt-snowflake.ps1" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Build snowflake-client
command: |
if ((Test-Path -Path 'C:\Users\circleci\project\desktop\onionshare\resources\tor\snowflake-client.exe') -eq $True) {
Write-Output "snowflake already built"
} else {
cd C:\Users\circleci\project\desktop
.\scripts\build-pt-snowflake.ps1
}
- save_cache:
key: build-win64-snowflake-{{ checksum "~/project/desktop/scripts/build-pt-snowflake.ps1" }}-{{ .Environment.CACHE_VERSION }}
paths:
- C:\Users\circleci\project\desktop\onionshare\resources\tor\snowflake-client.exe
- restore_cache:
key: build-win64-meek-{{ checksum "~/project/desktop/scripts/build-pt-meek.ps1" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Build meek-client
command: |
if ((Test-Path -Path 'C:\Users\circleci\project\desktop\onionshare\resources\tor\meek-client.exe') -eq $True) {
Write-Output "snowflake already built"
} else {
cd C:\Users\circleci\project\desktop
.\scripts\build-pt-meek.ps1
}
- save_cache:
key: build-win64-meek-{{ checksum "~/project/desktop/scripts/build-pt-meek.ps1" }}-{{ .Environment.CACHE_VERSION }}
paths:
- C:\Users\circleci\project\desktop\onionshare\resources\tor\meek-client.exe
- run:
name: Build OnionShare
command: |
cd ~\project\desktop
C:\Python39\Scripts\poetry run python .\setup-freeze.py build
C:\Python39\Scripts\poetry run python .\scripts\build-windows.py cleanup-build
- run:
name: Compress
command: |
mv ~\project\desktop\build\exe.win-amd64-3.9\ ~\onionshare-win64
Compress-Archive -LiteralPath ~\onionshare-win64 -DestinationPath ~\onionshare-win64.zip
- store_artifacts:
path: ~\onionshare-win64.zip
build-win32:
executor:
name: win/default
shell: powershell.exe
steps:
- checkout
- run:
name: Install Python 3.9.13 (32-bit)
command: |
cd ~\Downloads
Invoke-WebRequest -Uri https://www.python.org/ftp/python/3.9.13/python-3.9.13.exe -OutFile python-3.9.13.exe
.\python-3.9.13.exe /quiet InstallAllUsers=1 TargetDir=C:\Python39
while($true) {
if ((Test-Path -Path C:\Python39\python.exe) -eq $True) {
Write-Output "Python is installed"
break
} else {
Write-Output "Waiting for Python to finish installing ..."
Start-Sleep -Seconds 2
}
}
- run:
name: Install poetry
command: C:\Python39\python -m pip install poetry
# - restore_cache:
# key: build-win32-desktop-poetry-deps-{{ checksum "~/project/desktop/poetry.lock" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Install poetry dependencies
command: |
cd ~\project\desktop
C:\Python39\Scripts\poetry install
# - save_cache:
# key: build-win32-desktop-poetry-deps-{{ checksum "~/project/desktop/poetry.lock" }}-{{ .Environment.CACHE_VERSION }}
# paths:
# - C:\Users\circleci\AppData\Local\pypoetry\Cache\virtualenvs
- restore_cache:
key: get-tor-win32-{{ checksum "desktop/scripts/get-tor.py" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Get tor binaries from Tor Browser (32-bit)
command: |
cd desktop
C:\Python39\Scripts\poetry run python .\scripts\get-tor.py win32
- save_cache:
key: get-tor-win32-{{ checksum "desktop/scripts/get-tor.py" }}-{{ .Environment.CACHE_VERSION }}
paths:
- C:\Users\circleci\project\desktop\build\tor
- run:
name: Install golang (32-bit)
command: |
cd ~\Downloads
Invoke-WebRequest -Uri https://go.dev/dl/go1.18.windows-386.msi -OutFile go1.18.windows-386.msi
msiexec.exe /i go1.18.windows-386.msi /quiet /L*V go-install.log
- restore_cache:
key: build-win32-obfs4proxy-{{ checksum "~/project/desktop/scripts/build-pt-obfs4proxy.ps1" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Build obfs4proxy
command: |
if ((Test-Path -Path C:\Users\circleci\project\desktop\onionshare\resources\tor\obfs4proxy.exe) -eq $True) {
Write-Output "obfs4proxy already built"
} else {
$env:PATH = "C:\Program Files (x86)\Go\bin\go;$env:PATH"
cd C:\Users\circleci\project\desktop
.\scripts\build-pt-obfs4proxy.ps1
}
- save_cache:
key: build-win32-obfs4proxy-{{ checksum "~/project/desktop/scripts/build-pt-obfs4proxy.ps1" }}-{{ .Environment.CACHE_VERSION }}
paths:
- C:\Users\circleci\project\desktop\onionshare\resources\tor\obfs4proxy.exe
- restore_cache:
key: build-win32-snowflake-{{ checksum "~/project/desktop/scripts/build-pt-snowflake.ps1" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Build snowflake-client
command: |
if ((Test-Path -Path C:\Users\circleci\project\desktop\onionshare\resources\tor\snowflake-client.exe) -eq $True) {
Write-Output "snowflake already built"
} else {
$env:PATH = "C:\Program Files (x86)\Go\bin\go;$env:PATH"
cd C:\Users\circleci\project\desktop
.\scripts\build-pt-snowflake.ps1
}
- save_cache:
key: build-win32-snowflake-{{ checksum "~/project/desktop/scripts/build-pt-snowflake.ps1" }}-{{ .Environment.CACHE_VERSION }}
paths:
- C:\Users\circleci\project\desktop\onionshare\resources\tor\snowflake-client.exe
- restore_cache:
key: build-win32-meek-{{ checksum "~/project/desktop/scripts/build-pt-meek.ps1" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Build meek-client
command: |
if ((Test-Path -Path C:\Users\circleci\project\desktop\onionshare\resources\tor\meek-client.exe) -eq $True) {
Write-Output "snowflake already built"
} else {
$env:PATH = "C:\Program Files (x86)\Go\bin\go;$env:PATH"
cd C:\Users\circleci\project\desktop
.\scripts\build-pt-meek.ps1
}
- save_cache:
key: build-win32-meek-{{ checksum "~/project/desktop/scripts/build-pt-meek.ps1" }}-{{ .Environment.CACHE_VERSION }}
paths:
- C:\Users\circleci\project\desktop\onionshare\resources\tor\meek-client.exe
- run:
name: Build OnionShare
command: |
cd ~\project\desktop
C:\Python39\Scripts\poetry run python .\setup-freeze.py build
C:\Python39\Scripts\poetry run python .\scripts\build-windows.py cleanup-build
- run:
name: Compress
command: |
mv ~\project\desktop\build\exe.win32-3.9\ ~\onionshare-win32
Compress-Archive -LiteralPath ~\onionshare-win32 -DestinationPath ~\onionshare-win32.zip
- store_artifacts:
path: ~\onionshare-win32.zip
build-macos:
macos:
xcode: 12.5.1
environment:
BINARY_DIR: /Users/distiller/bin
steps:
- checkout
- run:
name: Install Go 1.18.3
command: |
curl -L https://go.dev/dl/go1.18.3.darwin-amd64.pkg --output ~/Downloads/go.pkg
sudo installer -pkg ~/Downloads/go.pkg -target /
- run:
name: Install Python 3.9.13
command: |
curl -L https://www.python.org/ftp/python/3.9.13/python-3.9.13-macosx10.9.pkg --output ~/Downloads/python.pkg
sudo installer -pkg ~/Downloads/python.pkg -target /
- run:
name: Install poetry
command: |
pip3 install poetry
ln -s /Library/Frameworks/Python.framework/Versions/3.9/bin/poetry /usr/local/bin
- restore_cache:
key: build-macos-desktop-poetry-deps-{{ checksum "~/project/desktop/poetry.lock" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Install poetry dependencies
command: |
cd ~/project/desktop
poetry install
- save_cache:
key: build-macos-desktop-poetry-deps-{{ checksum "~/project/desktop/poetry.lock" }}-{{ .Environment.CACHE_VERSION }}
paths:
- /Users/distiller/Library/Caches/pypoetry/virtualenvs
- restore_cache:
key: get-tor-macos-{{ checksum "desktop/scripts/get-tor.py" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Get tor binaries from Tor Browser
command: |
cd desktop
poetry run python ./scripts/get-tor.py macos
- save_cache:
key: get-tor-macos-{{ checksum "desktop/scripts/get-tor.py" }}-{{ .Environment.CACHE_VERSION }}
paths:
- ~/project/desktop/build/tor
- restore_cache:
key: build-macos-obfs4proxy-{{ checksum "~/project/desktop/scripts/build-pt-obfs4proxy.sh" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Build obfs4proxy
command: |
if [[ -f "/Users/distiller/project/desktop/onionshare/resources/tor/obfs4proxy" ]]; then
echo "obfs4proxy already built"
else
cd /Users/distiller/project/desktop
./scripts/build-pt-obfs4proxy.sh
fi
- save_cache:
key: build-macos-obfs4proxy-{{ checksum "~/project/desktop/scripts/build-pt-obfs4proxy.sh" }}-{{ .Environment.CACHE_VERSION }}
paths:
- /Users/distiller/project/desktop/onionshare/resources/tor/obfs4proxy
- restore_cache:
key: build-macos-snowflake-{{ checksum "~/project/desktop/scripts/build-pt-snowflake.sh" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Build snowflake
command: |
if [[ -f "/Users/distiller/project/desktop/onionshare/resources/tor/snowflake-client" ]]; then
echo "snowflake already built"
else
cd /Users/distiller/project/desktop
./scripts/build-pt-snowflake.sh
fi
- save_cache:
key: build-macos-snowflake-{{ checksum "~/project/desktop/scripts/build-pt-snowflake.sh" }}-{{ .Environment.CACHE_VERSION }}
paths:
- ~/project/desktop/onionshare/resources/tor/snowflake-client
- restore_cache:
key: build-macos-meek-{{ checksum "~/project/desktop/scripts/build-pt-obfs4proxy.sh" }}-{{ .Environment.CACHE_VERSION }}
- run:
name: Build meek
command: |
if [[ -f "/Users/distiller/project/desktop/onionshare/resources/tor/meek-client" ]]; then
echo "meek already built"
else
cd /Users/distiller/project/desktop
./scripts/build-pt-meek.sh
fi
- save_cache:
key: build-macos-meek-{{ checksum "~/project/desktop/scripts/build-pt-meek.sh" }}-{{ .Environment.CACHE_VERSION }}
paths:
- ~/project/desktop/onionshare/resources/tor/meek-client
- run:
name: Build OnionShare
command: |
cd ~/project/desktop
poetry run python ./setup-freeze.py build
poetry run python ./setup-freeze.py bdist_mac
poetry run python ./scripts/build-macos.py cleanup-build
- run:
name: Compress
command: |
cd ~/project/desktop/build
tar -czvf ~/onionshare-macos.tar.gz OnionShare.app
- store_artifacts:
path: ~/onionshare-macos.tar.gz

View file

@ -1,5 +1,9 @@
# OnionShare Changelog
## 2.6
* TODO
## 2.5
* Security fix: Sanitize the path parameter in History item widget to be plain text

View file

@ -16,12 +16,6 @@ Before making a release, you must update the version in these places:
If you update `flask-socketio`, ensure that you also update the [socket.io.min.js](https://github.com/micahflee/onionshare/blob/develop/cli/onionshare_cli/resources/static/js/socket.io.min.js) file to a version that is [supported](https://flask-socketio.readthedocs.io/en/latest/#version-compatibility) by the updated version of `flask-socketio`.
Use tor binaries from the latest Tor Browser:
- [ ] `desktop/scripts/get-tor-linux.py`
- [ ] `desktop/scripts/get-tor-osx.py`
- [ ] `desktop/scripts/get-tor-windows.py`
Update the documentation:
- [ ] Update all of the documentation in `docs` to cover new features, including taking new screenshots if necessary
@ -100,37 +94,61 @@ snapcraft upload --release=stable onionshare_${VERSION}_amd64.snap
## Windows
Set up the development environment described in desktop `README.md`.
Set up the packaging environment:
- To get `signtool.exe`, install the [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) and add `C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x86` to your path.
- Install the [Windows SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/) and add `C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x64` to your path (to get `signtool.exe`).
- Go to https://dotnet.microsoft.com/download/dotnet-framework and download and install .NET Framework 3.5 SP1 Runtime. I downloaded `dotnetfx35.exe`.
- Go to https://wixtoolset.org/releases/ and download and install WiX toolset. I downloaded `wix311.exe`. Add `C:\Program Files (x86)\WiX Toolset v3.11\bin` to the path.
Run the Windows build script:
CircleCI will build the binaries. Find the CircleCI jobs `build-win32` and `build-win64`, switch to the artifacts tab, and download:
- `onionshare-win32.zip`
- `onionshare-win64.zip`
Extract these files, change to the `desktop` folder, and run:
```
poetry run python .\package\build-windows.py
poetry run python .\scripts\build-windows.py codesign [onionshare_win32_path] [onionshare_win64_path]
poetry run python .\scripts\build-windows.py package [onionshare_win32_path] [onionshare_win64_path]
```
This will create `desktop/dist/OnionShare-$VERSION.msi`, signed.
This will create:
- `desktop/dist/OnionShare-win32-$VERSION.msi`
- `desktop/dist/OnionShare-win64-$VERSION.msi`
## macOS
Set up the development environment described in `README.md`.
Set up the packaging environment:
Then build an executable, make it a macOS app bundle, and package it in a dmg:
- Install create-dmg: `brew install create-dmg`
CircleCI will build the binaries. Find the CircleCI job `build-macos`, switch to the artifacts tab, and download:
- `onionshare-macos.zip`
Extract these files, change to the `desktop` folder, and run:
```sh
poetry run ./package/build-mac.py
poetry run python ./scripts/build-macos.py codesign [app_path]
poetry run python ./scripts/build-macos.py package [app_path]
```
The will create `dist/OnionShare-$VERSION.dmg`.
Now, notarize the release. You must have an app-specific Apple ID password saved in the login keychain called `onionshare-notarize`.
Now, notarize the release.
- Notarize it: `xcrun altool --notarize-app --primary-bundle-id "com.micahflee.onionshare" -u "micah@micahflee.com" -p "@keychain:onionshare-notarize" --file dist/OnionShare-$VERSION.dmg`
- Wait for it to get approved, check status with: `xcrun altool --notarization-history 0 -u "micah@micahflee.com" -p "@keychain:onionshare-notarize"`
- After it's approved, staple the ticket: `xcrun stapler staple dist/OnionShare-$VERSION.dmg`
```sh
export APPLE_PASSWORD="changeme" # app-specific Apple ID password
export VERSION=$(cat ../cli/onionshare_cli/resources/version.txt)
# Notarize it
xcrun altool --notarize-app --primary-bundle-id "com.micahflee.onionshare" -u "micah@micahflee.com" -p "$APPLE_PASSWORD" --file dist/OnionShare-$VERSION.dmg
# Wait for it to get approved, ceck status with
xcrun altool --notarization-history 0 -u "micah@micahflee.com" -p "$APPLE_PASSWORD"
# After it's approved, staple the ticket
xcrun stapler staple dist/OnionShare-$VERSION.dmg
```
This will create `desktop/dist/OnionShare-$VERSION.dmg`, signed and notarized.

View file

@ -332,21 +332,27 @@ class Common:
# In Windows, the Tor binaries are in the onionshare package, not the onionshare_cli package
base_path = self.get_resource_path("tor")
base_path = base_path.replace("onionshare_cli", "onionshare")
tor_path = os.path.join(base_path, "Tor", "tor.exe")
tor_path = os.path.join(base_path, "tor.exe")
# If tor.exe isn't there, mayber we're running from the source tree
if not os.path.exists(tor_path):
self.log(
"Common", "get_tor_paths", f"Cannot find tor.exe at {tor_path}"
)
base_path = os.path.join(os.getcwd(), "onionshare", "resources", "tor")
tor_path = os.path.join(base_path, "Tor", "tor.exe")
tor_path = os.path.join(base_path, "tor.exe")
if not os.path.exists(tor_path):
self.log(
"Common", "get_tor_paths", f"Cannot find tor.exe at {tor_path}"
)
raise CannotFindTor()
obfs4proxy_file_path = os.path.join(base_path, "Tor", "obfs4proxy.exe")
snowflake_file_path = os.path.join(base_path, "Tor", "snowflake-client.exe")
meek_client_file_path = os.path.join(base_path, "Tor", "meek-client.exe")
tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip")
tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy.exe")
snowflake_file_path = os.path.join(base_path, "snowflake-client.exe")
meek_client_file_path = os.path.join(base_path, "meek-client.exe")
tor_geo_ip_file_path = os.path.join(base_path, "geoip")
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
elif self.platform == "Darwin":
# Let's see if we have tor binaries in the onionshare GUI package
@ -499,7 +505,7 @@ class Common:
if valid_bridges:
return valid_bridges
else:
return False
return False
def is_flatpak(self):
"""
@ -513,7 +519,6 @@ class Common:
"""
return os.environ.get("SNAP_INSTANCE_NAME") == "onionshare"
@staticmethod
def random_string(num_bytes, output_len=None):
"""

View file

@ -1 +1 @@
2.5
2.6.dev1

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "onionshare_cli"
version = "2.5"
version = "2.6"
description = "OnionShare lets you securely and anonymously send and receive files. It works by starting a web server, making it accessible as a Tor onion service, and generating an unguessable web address so others can download files from you, or upload files to you. It does _not_ require setting up a separate server or using a third party file-sharing service."
authors = ["Micah Lee <micah@micahflee.com>"]
license = "GPLv3+"

View file

@ -9,62 +9,54 @@ git clone https://github.com/onionshare/onionshare.git
cd onionshare/desktop
```
Make sure you have Python 3 installed. If you're using Windows or macOS, install version 3.9.9 [from python.org](https://www.python.org/downloads/release/python-3912/). For Windows, make sure to install the 32-bit (x86) version, and to check the box to add python to the path on the first page of the installer.
Make sure you have Python 3 installed. If you're using Windows or macOS, install version 3.9.13 [from python.org](https://www.python.org/downloads/release/python-3913/). For Windows, make sure to check the box to add python to the path on the first page of the installer.
Make sure you have [poetry installed](https://python-poetry.org/docs/#installation), and then install the dependencies:
Make sure you have [poetry](https://python-poetry.org/) installed:
```
pip3 install poetry
```
And install the poetry dependencies:
```sh
poetry install
```
In Windows, you may need to install [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/), making sure to check "Desktop development with C++", before `poetry install` will work properly.
**Windows users:** You may need to install [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/), making sure to check "Desktop development with C++", before `poetry install` will work properly.
### Install platform-specific dependencies
### Get Tor
#### Linux
**Linux users:** In Ubuntu 20.04 you need the `libxcb-xinerama0` package installed.
In Ubuntu 20.04 you need the `libxcb-xinerama0` package installed.
**Windows users:** Download and install 7-Zip from https://7-zip.org/download.html. [Add](https://medium.com/@kevinmarkvi/how-to-add-executables-to-your-path-in-windows-5ffa4ce61a53) `C:\Program Files (x86)\7-Zip` to your path.
Download Tor Browser and extract the binaries:
Download Tor Browser and extract the binaries for your platform. The platform must be `win32`, `win64`, `macos`, or `linux64`.
```sh
poetry run ./scripts/get-tor-linux.py
```
#### macOS
Download Tor Browser and extract the binaries:
```sh
poetry run ./scripts/get-tor-osx.py
```
#### Windows
These instructions include adding folders to the path in Windows. To do this, go to Start and type "advanced system settings", and open "View advanced system settings" in the Control Panel. Click Environment Variables. Under "System variables" double-click on Path. From there you can add and remove folders that are available in the PATH.
Download and install 7-Zip from https://7-zip.org/download.html. I downloaded `7z1900.exe`. Add `C:\Program Files (x86)\7-Zip` to your path.
Download Tor Browser and extract the binaries:
```sh
poetry run python scripts\get-tor-windows.py
poetry run python ./scripts/get-tor.py [platform]
```
### Compile dependencies
Install Go. The simplest way to make sure everything works is to install Go by following [these instructions](https://golang.org/doc/install). (In Windows, make sure to install the 32-bit version of Go, such as `go1.17.5.windows-386.msi`.)
Install Go. The simplest way to make sure everything works is to install Go by following [these instructions](https://golang.org/doc/install).
Download and compile `meek-client`:
Compile pluggable transports:
```sh
./scripts/build-meek-client.py
```
Or in Windows:
**Windows users, in PowerShell:**
```powershell
python .\scripts\build-meek-client.py
.\scripts\build-pt-obfs4proxy.ps1
.\scripts\build-pt-snowflake.ps1
.\scripts\build-pt-meek.ps1
```
**macOS and Linux users:**
```sh
./scripts/build-pt-obfs4proxy.sh
./scripts/build-pt-snowflake.sh
./scripts/build-pt-meek.sh
```
### Running OnionShare from the source code tree

View file

@ -507,12 +507,12 @@ class GuiCommon:
if self.common.platform == "Windows":
base_path = self.get_resource_path("tor")
tor_path = os.path.join(base_path, "Tor", "tor.exe")
obfs4proxy_file_path = os.path.join(base_path, "Tor", "obfs4proxy.exe")
snowflake_file_path = os.path.join(base_path, "Tor", "snowflake-client.exe")
meek_client_file_path = os.path.join(base_path, "Tor", "meek-client.exe")
tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip")
tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
tor_path = os.path.join(base_path, "tor.exe")
obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy.exe")
snowflake_file_path = os.path.join(base_path, "snowflake-client.exe")
meek_client_file_path = os.path.join(base_path, "meek-client.exe")
tor_geo_ip_file_path = os.path.join(base_path, "geoip")
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
elif self.common.platform == "Darwin":
base_path = self.get_resource_path("tor")
tor_path = os.path.join(base_path, "tor")
@ -613,7 +613,9 @@ class ToggleCheckbox(QtWidgets.QCheckBox):
x = (
rect.width() - rect.x() - self.w + 20
) # 20 is the padding between text and toggle
y = self.height() / 2 - self.h / 2 + 16 # 16 is the padding top for the checkbox
y = (
self.height() / 2 - self.h / 2 + 16
) # 16 is the padding top for the checkbox
self.toggleRect = QtCore.QRect(x, y, self.w, self.h)
painter.setBrush(QtGui.QColor(self.bg_color))
painter.drawRoundedRect(x, y, self.w, self.h, self.h / 2, self.h / 2)

View file

@ -24,6 +24,6 @@
<update_contact>micah@micahflee.com</update_contact>
<content_rating type="oars-1.1" />
<releases>
<release type="development" date="2022-01-17" version="2.5" />
<release type="development" date="2022-06-19" version="2.6" />
</releases>
</component>

View file

@ -1,308 +0,0 @@
#!/usr/bin/env python3
import os
import inspect
import subprocess
import shutil
import itertools
import glob
root = os.path.dirname(
os.path.dirname(
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
)
def run(cmd, cwd=None, error_ok=False):
print(f"{cmd} # cwd={cwd}")
subprocess.run(cmd, cwd=cwd, check=True)
def get_size(dir):
size = 0
for path, dirs, files in os.walk(dir):
for f in files:
fp = os.path.join(path, f)
size += os.path.getsize(fp)
return size
def codesign(path, entitlements, identity):
run(
[
"codesign",
"--sign",
identity,
"--entitlements",
str(entitlements),
"--timestamp",
"--deep",
"--force",
"--options",
"runtime,library",
str(path),
]
)
def main():
desktop_dir = f"{root}/desktop"
app_dir = f"{desktop_dir}/build/OnionShare.app"
print("○ Clean up from last build")
if os.path.exists(f"{desktop_dir}/build"):
shutil.rmtree(f"{desktop_dir}/build")
if os.path.exists(f"{desktop_dir}/dist"):
shutil.rmtree(f"{desktop_dir}/dist")
print("○ Building binaries")
run(
[
shutil.which("python"),
"setup-freeze.py",
"bdist_mac",
],
desktop_dir,
)
before_size = get_size(f"{app_dir}")
print("○ Delete unused Qt Frameworks")
for framework in [
"Qt3DAnimation",
"Qt3DCore",
"Qt3DExtras",
"Qt3DInput",
"Qt3DLogic",
"Qt3DQuick",
"Qt3DQuickAnimation",
"Qt3DQuickExtras",
"Qt3DQuickInput",
"Qt3DQuickRender",
"Qt3DQuickScene2D",
"Qt3DRender",
"QtBluetooth",
"QtBodymovin",
"QtCharts",
"QtConcurrent",
"QtDataVisualization",
"QtDesigner",
"QtDesignerComponents",
"QtGamepad",
"QtHelp",
"QtLocation",
"QtMultimedia",
"QtMultimediaQuick",
"QtMultimediaWidgets",
"QtNetwork",
"QtNetworkAuth",
"QtNfc",
"QtOpenGL",
"QtPdf",
"QtPdfWidgets",
"QtPositioning",
"QtPositioningQuick",
"QtPrintSupport",
"QtPurchasing",
"QtQml",
"QtQmlModels",
"QtQmlWorkerScript",
"QtQuick",
"QtQuick3D",
"QtQuick3DAssetImport",
"QtQuick3DRender",
"QtQuick3DRuntimeRender",
"QtQuick3DUtils",
"QtQuickControls2",
"QtQuickParticles",
"QtQuickShapes",
"QtQuickTemplates2",
"QtQuickTest",
"QtQuickWidgets",
"QtRemoteObjects",
"QtRepParser",
"QtScript",
"QtScriptTools",
"QtScxml",
"QtSensors",
"QtSerialBus",
"QtSerialPort",
"QtSql",
"QtSvg",
"QtTest",
"QtTextToSpeech",
"QtUiPlugin",
"QtVirtualKeyboard",
"QtWebChannel",
"QtWebEngine",
"QtWebEngineCore",
"QtWebEngineWidgets",
"QtWebSockets",
"QtWebView",
"QtXml",
"QtXmlPatterns",
]:
shutil.rmtree(
f"{app_dir}/Contents/MacOS/lib/PySide2/Qt/lib/{framework}.framework"
)
try:
os.remove(
f"{app_dir}/Contents/MacOS/lib/PySide2/{framework}.abi3.so"
)
os.remove(
f"{app_dir}/Contents/MacOS/lib/PySide2/{framework}.pyi"
)
except FileNotFoundError:
pass
print("○ Move files around so Apple will notarize")
# https://github.com/marcelotduarte/cx_Freeze/issues/594
# https://gist.github.com/TechnicalPirate/259a9c24878fcad948452cb148af2a2c#file-custom_bdist_mac-py-L415
# Move lib from MacOS into Resources
os.rename(
f"{app_dir}/Contents/MacOS/lib",
f"{app_dir}/Contents/Resources/lib",
)
run(
["ln", "-s", "../Resources/lib"],
cwd=f"{app_dir}/Contents/MacOS",
)
# Move frameworks from Resources/lib into Frameworks
os.makedirs(f"{app_dir}/Contents/Frameworks", exist_ok=True)
for framework_filename in glob.glob(
f"{app_dir}/Contents/Resources/lib/PySide2/Qt/lib/Qt*.framework"
):
basename = os.path.basename(framework_filename)
os.rename(framework_filename, f"{app_dir}/Contents/Frameworks/{basename}")
run(
["ln", "-s", f"../../../../../Frameworks/{basename}"],
cwd=f"{app_dir}/Contents/Resources/lib/PySide2/Qt/lib",
)
if os.path.exists(f"{app_dir}/Contents/Frameworks/{basename}/Resources"):
os.rename(
f"{app_dir}/Contents/Frameworks/{basename}/Resources",
f"{app_dir}/Contents/Frameworks/{basename}/Versions/5/Resources"
)
run(
["ln", "-s", "Versions/5/Resources"],
cwd=f"{app_dir}/Contents/Frameworks/{basename}",
)
run(
["ln", "-s", "5", "Current"],
cwd=f"{app_dir}/Contents/Frameworks/{basename}/Versions",
)
# Move Qt plugins
os.rename(
f"{app_dir}/Contents/Resources/lib/PySide2/Qt/plugins",
f"{app_dir}/Contents/Frameworks/plugins",
)
run(
["ln", "-s", "../../../../Frameworks/plugins"],
cwd=f"{app_dir}/Contents/Resources/lib/PySide2/Qt",
)
print("○ Delete more unused PySide2 stuff to save space")
for filename in [
f"{app_dir}/Contents/Resources/lib/PySide2/Designer.app",
f"{app_dir}/Contents/Resources/lib/PySide2/examples",
f"{app_dir}/Contents/Resources/lib/PySide2/glue",
f"{app_dir}/Contents/Resources/lib/PySide2/include",
f"{app_dir}/Contents/Resources/lib/PySide2/pyside2-lupdate",
f"{app_dir}/Contents/Resources/lib/PySide2/Qt/qml",
f"{app_dir}/Contents/Resources/lib/PySide2/libpyside2.abi3.5.15.dylib",
f"{app_dir}/Contents/Resources/lib/PySide2/Qt/lib/QtRepParser.framework",
f"{app_dir}/Contents/Resources/lib/PySide2/Qt/lib/QtUiPlugin.framework",
f"{app_dir}/Contents/Resources/lib/PySide2/Qt/lib/QtWebEngineCore.framework/Helpers",
f"{app_dir}/Contents/Resources/lib/shiboken2/libshiboken2.abi3.5.15.dylib",
f"{app_dir}/Contents/Resources/lib/shiboken2/docs",
f"{app_dir}/Contents/Resources/lib/PySide2/rcc",
f"{app_dir}/Contents/Resources/lib/PySide2/uic",
]:
if os.path.isdir(filename):
shutil.rmtree(filename)
elif os.path.isfile(filename):
os.remove(filename)
else:
print(f"Cannot delete, filename not found: {filename}")
after_size = get_size(f"{app_dir}")
freed_bytes = before_size - after_size
freed_mb = int(freed_bytes / 1024 / 1024)
print(f"○ Freed {freed_mb} mb")
print("○ Sign app bundle")
identity_name_application = "Developer ID Application: Micah Lee (N9B95FDWH4)"
entitlements_plist_path = f"{desktop_dir}/package/Entitlements.plist"
for path in itertools.chain(
glob.glob(f"{app_dir}/Contents/Resources/lib/**/*.so", recursive=True),
glob.glob(f"{app_dir}/Contents/Resources/lib/**/*.dylib", recursive=True),
[
f"{app_dir}/Contents/Frameworks/QtCore.framework/Versions/5/QtCore",
f"{app_dir}/Contents/Frameworks/QtDBus.framework/Versions/5/QtDBus",
f"{app_dir}/Contents/Frameworks/QtGui.framework/Versions/5/QtGui",
f"{app_dir}/Contents/Frameworks/QtMacExtras.framework/Versions/5/QtMacExtras",
f"{app_dir}/Contents/Frameworks/QtWidgets.framework/Versions/5/QtWidgets",
f"{app_dir}/Contents/Resources/lib/Python",
f"{app_dir}/Contents/Resources/lib/onionshare/resources/tor/meek-client",
f"{app_dir}/Contents/Resources/lib/onionshare/resources/tor/obfs4proxy",
f"{app_dir}/Contents/Resources/lib/onionshare/resources/tor/snowflake-client",
f"{app_dir}/Contents/Resources/lib/onionshare/resources/tor/tor",
f"{app_dir}/Contents/Resources/lib/onionshare/resources/tor/libevent-2.1.7.dylib",
f"{app_dir}/Contents/MacOS/onionshare",
f"{app_dir}/Contents/MacOS/onionshare-cli",
f"{app_dir}",
],
):
codesign(path, entitlements_plist_path, identity_name_application)
print(f"○ Signed app bundle: {app_dir}")
if not os.path.exists("/usr/local/bin/create-dmg"):
print("○ Error: create-dmg is not installed")
return
print("○ Create DMG")
version_filename = f"{root}/cli/onionshare_cli/resources/version.txt"
with open(version_filename) as f:
version = f.read().strip()
os.makedirs(f"{desktop_dir}/dist", exist_ok=True)
dmg_path = f"{desktop_dir}/dist/OnionShare-{version}.dmg"
run(
[
"create-dmg",
"--volname",
"OnionShare",
"--volicon",
f"{desktop_dir}/onionshare/resources/onionshare.icns",
"--window-size",
"400",
"200",
"--icon-size",
"100",
"--icon",
"OnionShare.app",
"100",
"70",
"--hide-extension",
"OnionShare.app",
"--app-drop-link",
"300",
"70",
dmg_path,
f"{app_dir}",
"--identity",
identity_name_application,
]
)
print(f"○ Finished building DMG: {dmg_path}")
if __name__ == "__main__":
main()

68
desktop/poetry.lock generated
View file

@ -62,7 +62,7 @@ python-versions = "*"
[[package]]
name = "certifi"
version = "2022.5.18.1"
version = "2022.6.15"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
@ -104,7 +104,7 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "colorama"
version = "0.4.4"
version = "0.4.5"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
@ -112,18 +112,24 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "cx-freeze"
version = "6.10"
version = "6.11.0"
description = "Create standalone executables from Python scripts"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\""}
cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\" and python_version < \"3.10\""}
importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""}
lief = {version = ">=0.11.5", markers = "sys_platform == \"win32\" and python_version < \"3.10\""}
lief = {version = ">=0.11.5", markers = "sys_platform == \"win32\""}
packaging = ">=21.0"
patchelf = {version = ">=0.12", markers = "sys_platform == \"linux\""}
[package.extras]
dev = ["bump2version (>=1.0.1)", "pre-commit (>=2.17.0)", "pylint (>=2.13.0)", "cibuildwheel (==2.6.0)"]
doc = ["sphinx (==4.5.0)", "sphinx-rtd-theme (==1.0.0)"]
test = ["nose (==1.3.7)", "pygments (==2.11.2)", "pytest (>=7.0.1)", "pytest-cov (==3.0.0)", "pytest-mock (>=3.6.1)", "pytest-timeout (>=1.4.2)"]
[[package]]
name = "cx-logging"
version = "3.0"
@ -323,7 +329,7 @@ python-versions = "*"
[[package]]
name = "onionshare-cli"
version = "2.5"
version = "2.6"
description = "OnionShare lets you securely and anonymously send and receive files. It works by starting a web server, making it accessible as a Tor onion service, and generating an unguessable web address so others can download files from you, or upload files to you. It does _not_ require setting up a separate server or using a third party file-sharing service."
category = "main"
optional = false
@ -353,7 +359,7 @@ url = "../cli"
name = "packaging"
version = "21.3"
description = "Core utilities for Python packages"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.6"
@ -452,7 +458,7 @@ tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"]
name = "pyparsing"
version = "3.0.7"
description = "Python parsing module"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.6"
@ -706,7 +712,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "1.1"
python-versions = ">=3.6.2,<3.11"
content-hash = "60276ce053d95d34415d8bee002e2c39f410ad5c97f2d1948e7ddbdc5da9cc56"
content-hash = "80aa2819ec2d5e4dd5acb42f178c196776e05fb7efa5d1b99e5c2626c2208cd9"
[metadata.files]
atomicwrites = [
@ -750,8 +756,8 @@ cepa = [
{file = "cepa-1.8.3.tar.gz", hash = "sha256:1dc6f0b324d37a2ed2ca274648ece8fd2c96a1d2f440f58c0ca17afd4b5ede7a"},
]
certifi = [
{file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"},
{file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"},
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
]
cffi = [
{file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
@ -814,21 +820,35 @@ click = [
{file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
cx-freeze = [
{file = "cx_Freeze-6.10-cp310-cp310-win32.whl", hash = "sha256:770e911f70b48e4f309a47e02aaa7e1f6dc659fd1c486f3eebe5c9e9fe70aec2"},
{file = "cx_Freeze-6.10-cp310-cp310-win_amd64.whl", hash = "sha256:8d936a872c124c2e2a40da2e898e4fa0d225a165ab606217f75136c14ee8e673"},
{file = "cx_Freeze-6.10-cp36-cp36m-win32.whl", hash = "sha256:2be07ffce2dba23618cf736476df6f9837d9887c85f4f92fb3fbfcd090de041a"},
{file = "cx_Freeze-6.10-cp36-cp36m-win_amd64.whl", hash = "sha256:59b4cb77f5c82613efbec03815b952a6fe73d4a1805bb370d57a8a1130a181ca"},
{file = "cx_Freeze-6.10-cp37-cp37m-win32.whl", hash = "sha256:f03928cbcf8282e688dc1f9fa421044c24f5b7492a7b0fe7f45d5cc6f24c1ebf"},
{file = "cx_Freeze-6.10-cp37-cp37m-win_amd64.whl", hash = "sha256:9c4b491affd10065bcdd20dc0ab5239f787affe6d61857811a937ed266faa213"},
{file = "cx_Freeze-6.10-cp38-cp38-win32.whl", hash = "sha256:1438db6d59be86e8483ea2bf2ff2f81374ca63d8a1d3a84706173cc1b98dd46f"},
{file = "cx_Freeze-6.10-cp38-cp38-win_amd64.whl", hash = "sha256:ec752333fa2e40347902730662785da0ec4576cec976552784a6f60a7a07b45d"},
{file = "cx_Freeze-6.10-cp39-cp39-win32.whl", hash = "sha256:08a537681dcd1bcde9742584e91d86ef001fe798386f2834a904652d5b2a468a"},
{file = "cx_Freeze-6.10-cp39-cp39-win_amd64.whl", hash = "sha256:a4d2cb00eec6bc72a419370b5b0b5d0f3adf4d8417eb89228981d470cf4c6af6"},
{file = "cx_Freeze-6.10.tar.gz", hash = "sha256:e5b71bf57b9881ac142fbebeae2c8b0d3294b56f6e48ab64032321e3b1a2ba27"},
{file = "cx_Freeze-6.11.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0fa63ae171ee34dd644331ad9cba0e1627036a11ef23b0ce8569229e4546762f"},
{file = "cx_Freeze-6.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8274f4d1c696e6e824f98423b4afa618db4eadecb554d2948c364cb524e46bde"},
{file = "cx_Freeze-6.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1897bb479e9ad1d5ff339eb2f59009f618e4651e33f1dd695500b36c29045c75"},
{file = "cx_Freeze-6.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9a3f3d7934b93f1a73579f4b19fc7ee37556bee46768e6e8c54ac0d469a8918"},
{file = "cx_Freeze-6.11.0-cp310-cp310-win32.whl", hash = "sha256:dfbb70832ecb461798d8d19ca110c1c2f8a622475d22ec49c143a8abf19339b0"},
{file = "cx_Freeze-6.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:09ccf1c96354d11f694f1a9f6887b069d40fcf853b1b819d2308747d62efebb7"},
{file = "cx_Freeze-6.11.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44cb609f7849176e06fe12bb52459de31a3b68fc1d72279e9b081f18c9276c7a"},
{file = "cx_Freeze-6.11.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:986e666a8473e699f2a4ba689cfa017a4955ed79a56e2093df55af8ca3bff56f"},
{file = "cx_Freeze-6.11.0-cp36-cp36m-win32.whl", hash = "sha256:07428f54a17cb6e8a889030a357edb6398c1c90b998564b90399e028f538930b"},
{file = "cx_Freeze-6.11.0-cp36-cp36m-win_amd64.whl", hash = "sha256:83bb02fa03e0c52fdb078b064cb1c17cf6c0410bd74e768ade10d1708c324b74"},
{file = "cx_Freeze-6.11.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:630d0d0d3bcdc69df9af0cc02d84a9ab4f9305bc74513058e30042bfc379dd20"},
{file = "cx_Freeze-6.11.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea2f4f5bea120f88d53ca32a71ccf1b6758dc82ed468f944338ed8535eec550e"},
{file = "cx_Freeze-6.11.0-cp37-cp37m-win32.whl", hash = "sha256:70835e9d6475fb9a7f207d74bd0f3be1bd2d59b6cd09941e032ae512e7fdedee"},
{file = "cx_Freeze-6.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0c141cba6f9e9e9923b1cfbbb0cb44655848bf50c57249d8058fe388c18116f3"},
{file = "cx_Freeze-6.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:04453a2de82f81356be1c183ee0ab2fbf024791451970dbdde4f9f1bf6301b6a"},
{file = "cx_Freeze-6.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4324f1abf6e0d0a017e9fabb0f3805acd08f5b3b7eb059395e08e7ff2b6e9b83"},
{file = "cx_Freeze-6.11.0-cp38-cp38-win32.whl", hash = "sha256:81cacbc22c0f3be8e3807b9690c73eb682b16e5a2358f6bd213af6bf88402061"},
{file = "cx_Freeze-6.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:c2315a1634108fc94398bef3ef63435d53c8ecd1cbcce9e05c4bcca61a833d62"},
{file = "cx_Freeze-6.11.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f4da77d44b910d4b18fbe95a487ddc081ebe5cd4f35971b496c1fff0420cf495"},
{file = "cx_Freeze-6.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:868837ca628fe9501c317e475f68d04f442f98e6174cb77b7ec413061e3613d7"},
{file = "cx_Freeze-6.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b92dda05bd7d5c1a2632de837ea738d73070af6effc035a016ab820ee1ec51a"},
{file = "cx_Freeze-6.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:920e6ff71b7fe1dd77d7a01b7de04a7856ba4803964ce2acab1962d2212a2746"},
{file = "cx_Freeze-6.11.0-cp39-cp39-win32.whl", hash = "sha256:81a6a56b8ad83635a441eb0049cff65993c53fcf7c46f49b4cf31998facb24d6"},
{file = "cx_Freeze-6.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:ee81a20340f830182fba3e42b32a0fb1e455324d6d6c3f6c3b3020d619e04c2d"},
{file = "cx_Freeze-6.11.0.tar.gz", hash = "sha256:c461fc3a33cbdfc2b6bbdd02cb0ea048d496e9e33067489cae778d929379bee8"},
]
cx-logging = [
{file = "cx_Logging-3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9fcd297e5c51470521c47eff0f86ba844aeca6be97e13c3e2114ebdf03fa3c96"},

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "onionshare"
version = "2.5"
version = "2.6"
description = "OnionShare lets you securely and anonymously send and receive files. It works by starting a web server, making it accessible as a Tor onion service, and generating an unguessable web address so others can download files from you, or upload files to you. It does _not_ require setting up a separate server or using a third party file-sharing service."
authors = ["Micah Lee <micah@micahflee.com>"]
license = "GPLv3+"
@ -13,6 +13,7 @@ qrcode = "*"
cx_freeze = "*"
[tool.poetry.dev-dependencies]
click = "*"
black = "*"
pytest = "*"
pytest-faulthandler = "*"

View file

@ -1,74 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2014-2022 Micah Lee, et al. <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import requests
class UpdateTorBridges:
"""
Update the built-in Tor Bridges in OnionShare's torrc templates.
"""
def __init__(self, root_path):
self.root_path = root_path
torrc_template_dir = os.path.join(
self.root_path, os.pardir, "cli/onionshare_cli/resources"
)
endpoint = "https://bridges.torproject.org/moat/circumvention/builtin"
r = requests.post(
endpoint,
headers={"Content-Type": "application/vnd.api+json"},
)
if r.status_code != 200:
print(
f"There was a problem fetching the latest built-in bridges: status_code={r.status_code}"
)
return False
result = r.json()
if "errors" in result:
print(
f"There was a problem fetching the latest built-in bridges: errors={result['errors']}"
)
return False
for bridge_type in ["meek-azure", "obfs4", "snowflake"]:
if result[bridge_type]:
if bridge_type == "meek-azure":
torrc_template_extension = "meek_lite_azure"
else:
torrc_template_extension = bridge_type
torrc_template = os.path.join(
self.root_path,
torrc_template_dir,
f"torrc_template-{torrc_template_extension}",
)
with open(torrc_template, "w") as f:
f.write(f"# Enable built-in {bridge_type} bridge\n")
bridges = result[bridge_type]
# Sorts the bridges numerically by IP, since they come back in
# random order from the API each time, and create noisy git diff.
bridges.sort(key=lambda s: s.split()[1])
for item in bridges:
f.write(f"Bridge {item}\n")

View file

@ -0,0 +1,321 @@
#!/usr/bin/env python3
import os
import inspect
import click
import subprocess
import shutil
import glob
import itertools
root = os.path.dirname(
os.path.dirname(
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
)
desktop_dir = os.path.join(root, "desktop")
identity_name_application = "Developer ID Application: Micah Lee (N9B95FDWH4)"
entitlements_plist_path = f"{desktop_dir}/package/Entitlements.plist"
def get_app_path():
return os.path.join(desktop_dir, "build", "OnionShare.app")
def run(cmd, cwd=None, error_ok=False):
print(f"{cmd} # cwd={cwd}")
subprocess.run(cmd, cwd=cwd, check=True)
def get_size(dir):
size = 0
for path, dirs, files in os.walk(dir):
for f in files:
fp = os.path.join(path, f)
size += os.path.getsize(fp)
return size
def sign(path, entitlements, identity):
run(
[
"codesign",
"--sign",
identity,
"--entitlements",
str(entitlements),
"--timestamp",
"--deep",
"--force",
"--options",
"runtime,library",
str(path),
]
)
@click.group()
def main():
"""
macOS build tasks
"""
@main.command()
def cleanup_build():
"""Delete unused PySide2 stuff to save space"""
app_path = get_app_path()
before_size = get_size(app_path)
print("> Delete unused Qt Frameworks")
for framework in [
"QtMultimediaQuick",
"QtQuickControls2",
"QtQuickParticles",
"QtRemoteObjects",
"Qt3DInput",
"QtPdfWidgets",
"QtScriptTools",
"QtNetworkAuth",
"QtDataVisualization",
"QtWebEngineCore",
"Qt3DQuickRender",
"Qt3DQuickExtras",
"QtQuick3DRender",
"QtDesigner",
"QtNfc",
"QtQuick3DAssetImport",
"QtBodymovin",
"QtWebEngineWidgets",
"QtQuickWidgets",
"Qt3DQuickInput",
"Qt3DQuickScene2D",
"QtUiPlugin",
"QtPdf",
"Qt3DRender",
"QtQuick3DRuntimeRender",
"QtHelp",
"QtPrintSupport",
"QtCharts",
"QtWebSockets",
"QtQuick3DUtils",
"QtQuickTemplates2",
"QtScript",
"QtPositioningQuick",
"Qt3DCore",
"QtLocation",
"QtXml",
"QtSerialPort",
"QtWebView",
"QtQuick",
"QtScxml",
"QtQml",
"Qt3DExtras",
"QtWebChannel",
"QtMultimedia",
"QtQmlWorkerScript",
"QtVirtualKeyboard",
"QtPurchasing",
"QtOpenGL",
"QtWebEngine",
"Qt3DQuick",
"QtTest",
"QtPositioning",
"QtBluetooth",
"QtQuick3D",
"Qt3DLogic",
"QtQuickShapes",
"QtQuickTest",
"QtNetwork",
"QtXmlPatterns",
"QtSvg",
"QtDesignerComponents",
"QtMultimediaWidgets",
"QtQmlModels",
"Qt3DQuickAnimation",
"QtSensors",
"Qt3DAnimation",
"QtRepParser",
"QtTextToSpeech",
"QtGamepad",
"QtSerialBus",
"QtSql",
"QtConcurrent"
]:
shutil.rmtree(
f"{app_path}/Contents/MacOS/lib/PySide2/Qt/lib/{framework}.framework"
)
print(
f"Deleted: {app_path}/Contents/MacOS/lib/PySide2/Qt/lib/{framework}.framework"
)
try:
os.remove(f"{app_path}/Contents/MacOS/lib/PySide2/{framework}.abi3.so")
print(f"Deleted: {app_path}/Contents/MacOS/lib/PySide2/{framework}.abi3.so")
except FileNotFoundError:
pass
try:
os.remove(f"{app_path}/Contents/MacOS/lib/PySide2/{framework}.pyi")
print(f"Deleted: {app_path}/Contents/MacOS/lib/PySide2/{framework}.pyi")
except FileNotFoundError:
pass
print("> Move files around so Apple will notarize")
# https://github.com/marcelotduarte/cx_Freeze/issues/594
# https://gist.github.com/TechnicalPirate/259a9c24878fcad948452cb148af2a2c#file-custom_bdist_mac-py-L415
# Move lib from MacOS into Resources
os.rename(
f"{app_path}/Contents/MacOS/lib",
f"{app_path}/Contents/Resources/lib",
)
run(
["ln", "-s", "../Resources/lib"],
cwd=f"{app_path}/Contents/MacOS",
)
# Move frameworks from Resources/lib into Frameworks
os.makedirs(f"{app_path}/Contents/Frameworks", exist_ok=True)
for framework_filename in glob.glob(
f"{app_path}/Contents/Resources/lib/PySide2/Qt/lib/Qt*.framework"
):
basename = os.path.basename(framework_filename)
os.rename(framework_filename, f"{app_path}/Contents/Frameworks/{basename}")
run(
["ln", "-s", f"../../../../../Frameworks/{basename}"],
cwd=f"{app_path}/Contents/Resources/lib/PySide2/Qt/lib",
)
if os.path.exists(f"{app_path}/Contents/Frameworks/{basename}/Resources"):
os.rename(
f"{app_path}/Contents/Frameworks/{basename}/Resources",
f"{app_path}/Contents/Frameworks/{basename}/Versions/5/Resources",
)
run(
["ln", "-s", "Versions/5/Resources"],
cwd=f"{app_path}/Contents/Frameworks/{basename}",
)
try:
run(
["ln", "-s", "5", "Current"],
cwd=f"{app_path}/Contents/Frameworks/{basename}/Versions",
)
except:
pass
# Move Qt plugins
os.rename(
f"{app_path}/Contents/Resources/lib/PySide2/Qt/plugins",
f"{app_path}/Contents/Frameworks/plugins",
)
run(
["ln", "-s", "../../../../Frameworks/plugins"],
cwd=f"{app_path}/Contents/Resources/lib/PySide2/Qt",
)
print("> Delete more unused PySide2 stuff to save space")
for filename in [
f"{app_path}/Contents/Resources/lib/PySide2/Designer.app",
f"{app_path}/Contents/Resources/lib/PySide2/examples",
f"{app_path}/Contents/Resources/lib/PySide2/glue",
f"{app_path}/Contents/Resources/lib/PySide2/include",
f"{app_path}/Contents/Resources/lib/PySide2/pyside2-lupdate",
f"{app_path}/Contents/Resources/lib/PySide2/rcc",
f"{app_path}/Contents/Resources/lib/PySide2/uic",
f"{app_path}/Contents/Resources/lib/PySide2/libpyside2.abi3.5.15.dylib",
f"{app_path}/Contents/Resources/lib/PySide2/Qt/qml",
f"{app_path}/Contents/Resources/lib/shiboken2/libshiboken2.abi3.5.15.dylib",
f"{app_path}/Contents/Resources/lib/shiboken2/docs",
]:
if os.path.isfile(filename) or os.path.islink(filename):
os.remove(filename)
print(f"Deleted: {filename}")
elif os.path.isdir(filename):
shutil.rmtree(filename)
print(f"Deleted: {filename}")
else:
print(f"Cannot delete, filename not found: {filename}")
after_size = get_size(f"{app_path}")
freed_bytes = before_size - after_size
freed_mb = int(freed_bytes / 1024 / 1024)
print(f"> Freed {freed_mb} mb")
@main.command()
@click.argument("app_path")
def codesign(app_path):
"""Sign macOS binaries before packaging"""
for path in itertools.chain(
glob.glob(f"{app_path}/Contents/Resources/lib/**/*.so", recursive=True),
glob.glob(f"{app_path}/Contents/Resources/lib/**/*.dylib", recursive=True),
[
f"{app_path}/Contents/Frameworks/QtCore.framework/Versions/5/QtCore",
f"{app_path}/Contents/Frameworks/QtDBus.framework/Versions/5/QtDBus",
f"{app_path}/Contents/Frameworks/QtGui.framework/Versions/5/QtGui",
f"{app_path}/Contents/Frameworks/QtMacExtras.framework/Versions/5/QtMacExtras",
f"{app_path}/Contents/Frameworks/QtWidgets.framework/Versions/5/QtWidgets",
f"{app_path}/Contents/Resources/lib/Python",
f"{app_path}/Contents/Resources/lib/onionshare/resources/tor/meek-client",
f"{app_path}/Contents/Resources/lib/onionshare/resources/tor/obfs4proxy",
f"{app_path}/Contents/Resources/lib/onionshare/resources/tor/snowflake-client",
f"{app_path}/Contents/Resources/lib/onionshare/resources/tor/tor",
f"{app_path}/Contents/Resources/lib/onionshare/resources/tor/libevent-2.1.7.dylib",
f"{app_path}/Contents/MacOS/onionshare",
f"{app_path}/Contents/MacOS/onionshare-cli",
f"{app_path}",
],
):
sign(path, entitlements_plist_path, identity_name_application)
print(f"> Signed app bundle: {app_path}")
@main.command()
@click.argument("app_path")
def package(app_path):
"""Build the DMG package"""
if not os.path.exists("/usr/local/bin/create-dmg"):
print("> Error: create-dmg is not installed")
return
print("> Create DMG")
version_filename = f"{root}/cli/onionshare_cli/resources/version.txt"
with open(version_filename) as f:
version = f.read().strip()
os.makedirs(f"{desktop_dir}/dist", exist_ok=True)
dmg_path = f"{desktop_dir}/dist/OnionShare-{version}.dmg"
run(
[
"create-dmg",
"--volname",
"OnionShare",
"--volicon",
f"{desktop_dir}/onionshare/resources/onionshare.icns",
"--window-size",
"400",
"200",
"--icon-size",
"100",
"--icon",
"OnionShare.app",
"100",
"70",
"--hide-extension",
"OnionShare.app",
"--app-drop-link",
"300",
"70",
dmg_path,
app_path,
"--identity",
identity_name_application,
]
)
print(f"> Finished building DMG: {dmg_path}")
if __name__ == "__main__":
main()

View file

@ -1,68 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2014-2022 Micah Lee, et al. <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
"""
This script downloads a pre-built tor binary to bundle with OnionShare.
In order to avoid a Mac gnupg dependency, I manually verify the signature
and hard-code the sha256 hash.
"""
import shutil
import os
import subprocess
import inspect
import platform
def main():
if shutil.which("go") is None:
print("Install go: https://golang.org/doc/install")
return
subprocess.run(
[
"go",
"install",
"git.torproject.org/pluggable-transports/meek.git/meek-client@v0.37.0",
]
)
root_path = os.path.dirname(
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
if platform.system() == "Windows":
dist_path = os.path.join(root_path, "onionshare", "resources", "tor", "Tor")
bin_filename = "meek-client.exe"
else:
dist_path = os.path.join(root_path, "onionshare", "resources", "tor")
bin_filename = "meek-client"
bin_path = os.path.join(os.path.expanduser("~"), "go", "bin", bin_filename)
shutil.copyfile(
os.path.join(bin_path),
os.path.join(dist_path, bin_filename),
)
os.chmod(os.path.join(dist_path, bin_filename), 0o755)
print(f"Installed {bin_filename} in {dist_path}")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,9 @@
$env:MEEK_TAG = 'v0.37.0'
New-Item -ItemType Directory -Force -Path .\build\meek
cd .\build\meek
git clone https://git.torproject.org/pluggable-transports/meek.git
cd meek
git checkout $MEEK_TAG
go build .\meek-client
Move-Item -Path .\meek-client.exe -Destination ..\..\..\onionshare\resources\tor\meek-client.exe

View file

@ -0,0 +1,9 @@
#!/bin/bash
MEEK_TAG=v0.37.0
mkdir -p ./build/meek
cd ./build/meek
git clone https://git.torproject.org/pluggable-transports/meek.git
cd meek
git checkout $MEEK_TAG
go build -o ../../../onionshare/resources/tor/meek-client ./meek-client

View file

@ -0,0 +1,9 @@
$env:OBFS4PROXY_TAG = 'obfs4proxy-0.0.13'
New-Item -ItemType Directory -Force -Path .\build\obfs4proxy
cd .\build\obfs4proxy
git clone https://gitlab.com/yawning/obfs4
cd obfs4
git checkout $OBFS4PROXY_TAG
go build .\obfs4proxy
Move-Item -Path .\obfs4proxy.exe -Destination ..\..\..\onionshare\resources\tor\obfs4proxy.exe

View file

@ -0,0 +1,9 @@
#!/bin/bash
OBFS4PROXY_TAG=obfs4proxy-0.0.13
mkdir -p ./build/obfs4proxy
cd ./build/obfs4proxy
git clone https://gitlab.com/yawning/obfs4 || echo "already cloned"
cd obfs4
git checkout $OBFS4PROXY_TAG
go build -o ../../../onionshare/resources/tor/obfs4proxy ./obfs4proxy

View file

@ -0,0 +1,9 @@
$env:SNOWFLAKE_TAG = 'v2.2.0'
New-Item -ItemType Directory -Force -Path .\build\snowflake
cd .\build\snowflake
git clone https://git.torproject.org/pluggable-transports/snowflake.git
cd snowflake
git checkout $SNOWFLAKE_TAG
go build .\client
Move-Item -Path .\client.exe -Destination ..\..\..\onionshare\resources\tor\snowflake-client.exe

View file

@ -0,0 +1,9 @@
#!/bin/bash
SNOWFLAKE_TAG=v2.2.0
mkdir -p ./build/snowflake
cd ./build/snowflake
git clone https://git.torproject.org/pluggable-transports/snowflake.git
cd snowflake
git checkout $SNOWFLAKE_TAG
go build -o ../../../onionshare/resources/tor/snowflake-client ./client

View file

@ -1,17 +1,37 @@
#!/usr/bin/env python3
from distutils.command.build import build
import sys
import os
import inspect
import subprocess
import click
import shutil
import subprocess
import uuid
import xml.etree.ElementTree as ET
root = os.path.dirname(
os.path.dirname(
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
)
desktop_dir = os.path.join(root, "desktop")
def get_build_path():
if "64 bit" in sys.version:
python_arch = "win-amd64"
else:
python_arch = "win32"
return os.path.join(desktop_dir, "build", f"exe.{python_arch}-3.9")
def get_size(dir):
size = 0
for path, dirs, files in os.walk(dir):
for f in files:
fp = os.path.join(path, f)
size += os.path.getsize(fp)
return size
def run(cmd, cwd=None, error_ok=False):
@ -23,7 +43,8 @@ def run(cmd, cwd=None, error_ok=False):
raise subprocess.CalledProcessError(e)
def sign(filename, cwd=None):
def sign(filename):
click.echo(f"> Signing {filename}")
run(
[
shutil.which("signtool"),
@ -32,7 +53,7 @@ def sign(filename, cwd=None):
"/d",
"OnionShare",
"/sha1",
"bb1d265ab02272e8fc742f149dcf8751cac63f50",
"1a0345732140749bdaa03efe8591b2c2a036884c",
"/fd",
"SHA256",
"/td",
@ -40,20 +61,10 @@ def sign(filename, cwd=None):
"/tr",
"http://timestamp.digicert.com",
filename,
],
cwd,
]
)
def get_size(dir):
size = 0
for path, dirs, files in os.walk(dir):
for f in files:
fp = os.path.join(path, f)
size += os.path.getsize(fp)
return size
def wix_build_data(dirname, dir_prefix, id_, name):
data = {
"id": id_,
@ -73,7 +84,7 @@ def wix_build_data(dirname, dir_prefix, id_, name):
id_prefix = id_
# Skip lib/Pyside2/Examples folder
if "\\build\\exe.win32-3.9\\lib\\PySide2\\examples" in dirname:
if "\\lib\\PySide2\\examples" in dirname:
continue
id_value = f"{id_prefix}{basename.capitalize().replace('-', '_')}"
@ -159,33 +170,169 @@ def wix_build_components_xml(root, data):
return component_ids
def main():
desktop_dir = os.path.join(root, "desktop")
def msi_package(build_path, msi_path, product_update_code):
print(f"> Build the WiX file")
version_filename = os.path.join(
build_path, "lib", "onionshare_cli", "resources", "version.txt"
)
with open(version_filename) as f:
version = f.read().strip()
# change a version like 2.6.dev1 to just 2.6, for cx_Freeze's sake
last_digit = version[-1]
if version.endswith(f".dev{last_digit}"):
version = version[0:-5]
print("○ Clean up from last build")
if os.path.exists(os.path.join(desktop_dir, "build")):
shutil.rmtree(os.path.join(desktop_dir, "build"))
if os.path.exists(os.path.join(desktop_dir, "dist")):
shutil.rmtree(os.path.join(desktop_dir, "dist"))
data = {
"id": "TARGETDIR",
"name": "SourceDir",
"dirs": [
{
"id": "ProgramFilesFolder",
"dirs": [],
},
{
"id": "ProgramMenuFolder",
"dirs": [],
},
],
}
print("○ Building binaries")
data["dirs"][0]["dirs"].append(
wix_build_data(
build_path,
".",
"INSTALLDIR",
"OnionShare",
)
)
root_el = ET.Element("Wix", xmlns="http://schemas.microsoft.com/wix/2006/wi")
product_el = ET.SubElement(
root_el,
"Product",
Name="OnionShare",
Manufacturer="Micah Lee, et al.",
Id="*",
UpgradeCode="$(var.ProductUpgradeCode)",
Language="1033",
Codepage="1252",
Version="$(var.ProductVersion)",
)
ET.SubElement(
product_el,
"Package",
Id="*",
Keywords="Installer",
Description="OnionShare $(var.ProductVersion) Installer",
Manufacturer="Micah Lee, et al.",
InstallerVersion="100",
Languages="1033",
Compressed="yes",
SummaryCodepage="1252",
)
ET.SubElement(product_el, "Media", Id="1", Cabinet="product.cab", EmbedCab="yes")
ET.SubElement(
product_el,
"Icon",
Id="ProductIcon",
SourceFile=os.path.join(
desktop_dir, "onionshare", "resources", "onionshare.ico"
),
)
ET.SubElement(product_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon")
ET.SubElement(
product_el,
"Property",
Id="ARPHELPLINK",
Value="https://docs.onionshare.org",
)
ET.SubElement(
product_el,
"Property",
Id="ARPURLINFOABOUT",
Value="https://onionshare.org",
)
ET.SubElement(product_el, "UIRef", Id="WixUI_Minimal")
ET.SubElement(product_el, "UIRef", Id="WixUI_ErrorProgressText")
ET.SubElement(
product_el,
"WixVariable",
Id="WixUILicenseRtf",
Value=os.path.join(desktop_dir, "package", "license.rtf"),
)
ET.SubElement(
product_el,
"WixVariable",
Id="WixUIDialogBmp",
Value=os.path.join(desktop_dir, "package", "dialog.bmp"),
)
ET.SubElement(
product_el,
"MajorUpgrade",
AllowSameVersionUpgrades="yes",
DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.",
)
wix_build_dir_xml(product_el, data)
component_ids = wix_build_components_xml(product_el, data)
feature_el = ET.SubElement(product_el, "Feature", Id="DefaultFeature", Level="1")
for component_id in component_ids:
ET.SubElement(feature_el, "ComponentRef", Id=component_id)
ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts")
with open(os.path.join(build_path, "OnionShare.wxs"), "w") as f:
f.write('<?xml version="1.0" encoding="windows-1252"?>\n')
f.write(f'<?define ProductVersion = "{version}"?>\n')
f.write(f'<?define ProductUpgradeCode = "{product_update_code}"?>\n')
ET.indent(root_el)
f.write(ET.tostring(root_el).decode())
print(f"> Build the MSI")
run(
[shutil.which("candle.exe"), "OnionShare.wxs"],
build_path,
)
run(
[shutil.which("light.exe"), "-ext", "WixUIExtension", "OnionShare.wixobj"],
build_path,
)
print(f"> Prepare OnionShare.msi for signing")
run(
[
shutil.which("python"),
"setup-freeze.py",
"build",
shutil.which("insignia.exe"),
"-im",
os.path.join(build_path, "OnionShare.msi"),
],
desktop_dir,
error_ok=True,
)
before_size = get_size(os.path.join(desktop_dir, "build", "exe.win32-3.9"))
sign(os.path.join(build_path, "OnionShare.msi"))
print(f"> Final MSI: {msi_path}")
os.makedirs(os.path.join(desktop_dir, "dist"), exist_ok=True)
os.rename(
os.path.join(build_path, "OnionShare.msi"),
msi_path,
)
@click.group()
def main():
"""
Windows build tasks
"""
@main.command()
def cleanup_build():
"""Delete unused PySide2 stuff to save space"""
build_path = get_build_path()
before_size = get_size(build_path)
print("○ Delete unused PySide2 stuff to save space")
for dirname in ["examples", "qml"]:
shutil.rmtree(
os.path.join(
desktop_dir, "build", "exe.win32-3.9", "lib", "PySide2", dirname
)
)
shutil.rmtree(os.path.join(build_path, "lib", "PySide2", dirname))
for filename in [
"lconvert.exe",
"linguist.exe",
@ -412,174 +559,86 @@ def main():
]:
os.remove(
os.path.join(
desktop_dir,
"build",
"exe.win32-3.9",
build_path,
"lib",
"PySide2",
filename.replace("/", "\\"),
)
)
after_size = get_size(os.path.join(desktop_dir, "build", "exe.win32-3.9"))
after_size = get_size(build_path)
freed_bytes = before_size - after_size
freed_mb = int(freed_bytes / 1024 / 1024)
print(f"Freed {freed_mb} mb")
print(f"Freed {freed_mb} mb")
print(f"○ Signing onionshare.exe")
sign(os.path.join("build", "exe.win32-3.9", "onionshare.exe"), desktop_dir)
print(f"○ Signing onionshare-cli.exe")
sign(os.path.join("build", "exe.win32-3.9", "onionshare-cli.exe"), desktop_dir)
@main.command()
@click.argument("win32_path")
@click.argument("win64_path")
def codesign(win32_path, win64_path):
"""Sign Windows binaries before packaging"""
paths = [win32_path, win64_path]
print(f"○ Build the WiX file")
for path in paths:
if not os.path.isdir(path):
click.echo("Invalid build path")
return
for path in paths:
sign(os.path.join(path, "onionshare.exe"))
sign(os.path.join(path, "onionshare-cli.exe"))
sign(
os.path.join(
path,
"lib",
"onionshare",
"resources",
"tor",
"meek-client.exe",
)
)
sign(
os.path.join(
path,
"lib",
"onionshare",
"resources",
"tor",
"obfs4proxy.exe",
)
)
sign(
os.path.join(
path,
"lib",
"onionshare",
"resources",
"tor",
"snowflake-client.exe",
)
)
@main.command()
@click.argument("win32_path")
@click.argument("win64_path")
def package(win32_path, win64_path):
"""Build the MSI package"""
version_filename = os.path.join(
root, "cli", "onionshare_cli", "resources", "version.txt"
)
with open(version_filename) as f:
version = f.read().strip()
dist_dir = os.path.join(
root,
"desktop",
"build",
"exe.win32-3.9",
msi_package(
win32_path,
os.path.join(desktop_dir, "dist", f"OnionShare-win32-{version}.msi"),
"12b9695c-965b-4be0-bc33-21274e809576",
)
data = {
"id": "TARGETDIR",
"name": "SourceDir",
"dirs": [
{
"id": "ProgramFilesFolder",
"dirs": [],
},
{
"id": "ProgramMenuFolder",
"dirs": [],
},
],
}
data["dirs"][0]["dirs"].append(
wix_build_data(
dist_dir,
"exe.win32-3.9",
"INSTALLDIR",
"OnionShare",
)
)
root_el = ET.Element("Wix", xmlns="http://schemas.microsoft.com/wix/2006/wi")
product_el = ET.SubElement(
root_el,
"Product",
Name="OnionShare",
Manufacturer="Micah Lee, et al.",
Id="*",
UpgradeCode="$(var.ProductUpgradeCode)",
Language="1033",
Codepage="1252",
Version="$(var.ProductVersion)",
)
ET.SubElement(
product_el,
"Package",
Id="*",
Keywords="Installer",
Description="OnionShare $(var.ProductVersion) Installer",
Manufacturer="Micah Lee, et al.",
InstallerVersion="100",
Languages="1033",
Compressed="yes",
SummaryCodepage="1252",
)
ET.SubElement(product_el, "Media", Id="1", Cabinet="product.cab", EmbedCab="yes")
ET.SubElement(
product_el,
"Icon",
Id="ProductIcon",
SourceFile="..\\onionshare\\resources\\onionshare.ico",
)
ET.SubElement(product_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon")
ET.SubElement(
product_el,
"Property",
Id="ARPHELPLINK",
Value="https://docs.onionshare.org",
)
ET.SubElement(
product_el,
"Property",
Id="ARPURLINFOABOUT",
Value="https://onionshare.org",
)
ET.SubElement(product_el, "UIRef", Id="WixUI_Minimal")
ET.SubElement(product_el, "UIRef", Id="WixUI_ErrorProgressText")
ET.SubElement(
product_el,
"WixVariable",
Id="WixUILicenseRtf",
Value="..\\package\\license.rtf",
)
ET.SubElement(
product_el,
"WixVariable",
Id="WixUIDialogBmp",
Value="..\\package\\dialog.bmp",
)
ET.SubElement(
product_el,
"MajorUpgrade",
AllowSameVersionUpgrades="yes",
DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.",
)
wix_build_dir_xml(product_el, data)
component_ids = wix_build_components_xml(product_el, data)
feature_el = ET.SubElement(product_el, "Feature", Id="DefaultFeature", Level="1")
for component_id in component_ids:
ET.SubElement(feature_el, "ComponentRef", Id=component_id)
ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts")
with open(os.path.join(root, "desktop", "build", "OnionShare.wxs"), "w") as f:
f.write('<?xml version="1.0" encoding="windows-1252"?>\n')
f.write(f'<?define ProductVersion = "{version}"?>\n')
f.write(
'<?define ProductUpgradeCode = "12b9695c-965b-4be0-bc33-21274e809576"?>\n'
)
ET.indent(root_el)
f.write(ET.tostring(root_el).decode())
print(f"○ Build the MSI")
run(
[shutil.which("candle.exe"), "OnionShare.wxs"],
os.path.join(desktop_dir, "build"),
)
run(
[shutil.which("light.exe"), "-ext", "WixUIExtension", "OnionShare.wixobj"],
os.path.join(desktop_dir, "build"),
)
print(f"○ Prepare OnionShare.msi for signing")
run(
[
shutil.which("insignia.exe"),
"-im",
os.path.join(desktop_dir, "build", "OnionShare.msi"),
],
error_ok=True,
)
sign(os.path.join(desktop_dir, "build", "OnionShare.msi"))
final_msi_filename = os.path.join(desktop_dir, "dist", f"OnionShare-{version}.msi")
print(f"○ Final MSI: {final_msi_filename}")
os.makedirs(os.path.join(desktop_dir, "dist"), exist_ok=True)
os.rename(
os.path.join(desktop_dir, "build", "OnionShare.msi"),
final_msi_filename,
msi_package(
win64_path,
os.path.join(desktop_dir, "dist", f"OnionShare-win64-{version}.msi"),
"ed7f9243-3528-4b4a-b85c-9943982e75eb",
)

View file

@ -1,135 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2014-2022 Micah Lee, et al. <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
"""
This script downloads a pre-built tor binary to bundle with OnionShare.
In order to avoid a Mac gnupg dependency, I manually verify the signature
and hard-code the sha256 hash.
"""
import inspect
import os
import sys
import hashlib
import shutil
import subprocess
import requests
from bridges import UpdateTorBridges
def main():
tarball_url = "https://dist.torproject.org/torbrowser/11.0.13/tor-browser-linux64-11.0.13_en-US.tar.xz"
tarball_filename = "tor-browser-linux64-11.0.13_en-US.tar.xz"
expected_tarball_sha256 = (
"df61fd90b7c1033cbb5856f3d076b5ca19f27e93c1a84741bd83b019dfe7ff0e"
)
# Build paths
root_path = os.path.dirname(
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
working_path = os.path.join(root_path, "build", "tor")
tarball_path = os.path.join(working_path, tarball_filename)
dist_path = os.path.join(root_path, "onionshare", "resources", "tor")
# Make sure dirs exist
if not os.path.exists(working_path):
os.makedirs(working_path, exist_ok=True)
if not os.path.exists(dist_path):
os.makedirs(dist_path, exist_ok=True)
# Make sure the tarball is downloaded
if not os.path.exists(tarball_path):
print("Downloading {}".format(tarball_url))
r = requests.get(tarball_url)
open(tarball_path, "wb").write(r.content)
tarball_sha256 = hashlib.sha256(r.content).hexdigest()
else:
tarball_data = open(tarball_path, "rb").read()
tarball_sha256 = hashlib.sha256(tarball_data).hexdigest()
# Compare the hash
if tarball_sha256 != expected_tarball_sha256:
print("ERROR! The sha256 doesn't match:")
print("expected: {}".format(expected_tarball_sha256))
print(" actual: {}".format(tarball_sha256))
sys.exit(-1)
# Delete extracted tarball, if it's there
shutil.rmtree(os.path.join(working_path, "tor-browser_en-US"), ignore_errors=True)
# Extract the tarball
subprocess.call(["tar", "-xvf", tarball_path], cwd=working_path)
tarball_tor_path = os.path.join(
working_path, "tor-browser_en-US", "Browser", "TorBrowser"
)
# Copy into dist
shutil.copyfile(
os.path.join(tarball_tor_path, "Data", "Tor", "geoip"),
os.path.join(dist_path, "geoip"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Data", "Tor", "geoip6"),
os.path.join(dist_path, "geoip6"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "tor"),
os.path.join(dist_path, "tor"),
)
os.chmod(os.path.join(dist_path, "tor"), 0o755)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libcrypto.so.1.1"),
os.path.join(dist_path, "libcrypto.so.1.1"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libevent-2.1.so.7"),
os.path.join(dist_path, "libevent-2.1.so.7"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libssl.so.1.1"),
os.path.join(dist_path, "libssl.so.1.1"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libstdc++", "libstdc++.so.6"),
os.path.join(dist_path, "libstdc++.so.6"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "PluggableTransports", "obfs4proxy"),
os.path.join(dist_path, "obfs4proxy"),
)
os.chmod(os.path.join(dist_path, "obfs4proxy"), 0o755)
shutil.copyfile(
os.path.join(
tarball_tor_path, "Tor", "PluggableTransports", "snowflake-client"
),
os.path.join(dist_path, "snowflake-client"),
)
os.chmod(os.path.join(dist_path, "snowflake-client"), 0o755)
print(f"Tor binaries extracted to: {dist_path}")
# Fetch the built-in bridges
UpdateTorBridges(root_path)
if __name__ == "__main__":
main()

View file

@ -1,123 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2014-2022 Micah Lee, et al. <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
"""
This script downloads a pre-built tor binary to bundle with OnionShare.
In order to avoid a Mac gnupg dependency, I manually verify the signature
and hard-code the sha256 hash.
"""
import inspect
import os
import sys
import hashlib
import shutil
import subprocess
import requests
from bridges import UpdateTorBridges
def main():
dmg_url = "https://dist.torproject.org/torbrowser/11.0.13/TorBrowser-11.0.13-osx64_en-US.dmg"
dmg_filename = "TorBrowser-11.0.13-osx64_en-US.dmg"
expected_dmg_sha256 = (
"3abd14f3e567a800f279b602dff1d886c2578d002d5e01f0bea6cee24e153c66"
)
# Build paths
root_path = os.path.dirname(
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
working_path = os.path.join(root_path, "build", "tor")
dmg_tor_path = os.path.join(
"/Volumes", "Tor Browser", "Tor Browser.app", "Contents"
)
dmg_path = os.path.join(working_path, dmg_filename)
dist_path = os.path.join(root_path, "onionshare", "resources", "tor")
if not os.path.exists(dist_path):
os.makedirs(dist_path, exist_ok=True)
# Make sure the working folder exists
if not os.path.exists(working_path):
os.makedirs(working_path)
# Make sure the zip is downloaded
if not os.path.exists(dmg_path):
print("Downloading {}".format(dmg_url))
r = requests.get(dmg_url)
open(dmg_path, "wb").write(r.content)
dmg_sha256 = hashlib.sha256(r.content).hexdigest()
else:
dmg_data = open(dmg_path, "rb").read()
dmg_sha256 = hashlib.sha256(dmg_data).hexdigest()
# Compare the hash
if dmg_sha256 != expected_dmg_sha256:
print("ERROR! The sha256 doesn't match:")
print("expected: {}".format(expected_dmg_sha256))
print(" actual: {}".format(dmg_sha256))
sys.exit(-1)
# Mount the dmg, copy data to the working path
subprocess.call(["hdiutil", "attach", dmg_path])
# Copy into dist
shutil.copyfile(
os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "geoip"),
os.path.join(dist_path, "geoip"),
)
shutil.copyfile(
os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "geoip6"),
os.path.join(dist_path, "geoip6"),
)
shutil.copyfile(
os.path.join(dmg_tor_path, "MacOS", "Tor", "tor.real"),
os.path.join(dist_path, "tor"),
)
os.chmod(os.path.join(dist_path, "tor"), 0o755)
shutil.copyfile(
os.path.join(dmg_tor_path, "MacOS", "Tor", "libevent-2.1.7.dylib"),
os.path.join(dist_path, "libevent-2.1.7.dylib"),
)
# obfs4proxy binary
shutil.copyfile(
os.path.join(dmg_tor_path, "MacOS", "Tor", "PluggableTransports", "obfs4proxy"),
os.path.join(dist_path, "obfs4proxy"),
)
os.chmod(os.path.join(dist_path, "obfs4proxy"), 0o755)
# snowflake-client binary
shutil.copyfile(
os.path.join(
dmg_tor_path, "MacOS", "Tor", "PluggableTransports", "snowflake-client"
),
os.path.join(dist_path, "snowflake-client"),
)
os.chmod(os.path.join(dist_path, "snowflake-client"), 0o755)
# Eject dmg
subprocess.call(["diskutil", "eject", "/Volumes/Tor Browser"])
# Fetch the built-in bridges
UpdateTorBridges(root_path)
if __name__ == "__main__":
main()

View file

@ -1,108 +0,0 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2014-2022 Micah Lee, et al. <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
"""
This script downloads a pre-built tor binary to bundle with OnionShare.
In order to avoid a Windows gnupg dependency, I manually verify the signature
and hard-code the sha256 hash.
"""
import inspect
import os
import sys
import hashlib
import shutil
import subprocess
import requests
from bridges import UpdateTorBridges
def main():
exe_url = "https://dist.torproject.org/torbrowser/11.0.13/torbrowser-install-11.0.13_en-US.exe"
exe_filename = "torbrowser-install-11.0.13_en-US.exe"
expected_exe_sha256 = (
"20dd6c7ec6caed5ec793e351bd025d8cd9819ddbb7a87b11a3b3128e92b7baa4"
)
# Build paths
root_path = os.path.dirname(
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
working_path = os.path.join(root_path, "build", "tor")
exe_path = os.path.join(working_path, exe_filename)
dist_path = os.path.join(root_path, "onionshare", "resources", "tor")
# Make sure the working folder exists
if not os.path.exists(working_path):
os.makedirs(working_path)
# Make sure Tor Browser is downloaded
if not os.path.exists(exe_path):
print("Downloading {}".format(exe_url))
r = requests.get(exe_url)
open(exe_path, "wb").write(r.content)
exe_sha256 = hashlib.sha256(r.content).hexdigest()
else:
exe_data = open(exe_path, "rb").read()
exe_sha256 = hashlib.sha256(exe_data).hexdigest()
# Compare the hash
if exe_sha256 != expected_exe_sha256:
print("ERROR! The sha256 doesn't match:")
print("expected: {}".format(expected_exe_sha256))
print(" actual: {}".format(exe_sha256))
sys.exit(-1)
# Extract the bits we need from the exe
subprocess.Popen(
[
"7z",
"e",
"-y",
exe_path,
"Browser\\TorBrowser\\Tor",
"-o%s" % os.path.join(working_path, "Tor"),
]
).wait()
subprocess.Popen(
[
"7z",
"e",
"-y",
exe_path,
"Browser\\TorBrowser\\Data\\Tor\\geoip*",
"-o%s" % os.path.join(working_path, "Data"),
]
).wait()
# Copy into the onionshare resources
if os.path.exists(dist_path):
shutil.rmtree(dist_path)
os.makedirs(dist_path)
shutil.copytree(os.path.join(working_path, "Tor"), os.path.join(dist_path, "Tor"))
shutil.copytree(
os.path.join(working_path, "Data"), os.path.join(dist_path, "Data", "Tor")
)
# Fetch the built-in bridges
UpdateTorBridges(root_path)
if __name__ == "__main__":
main()

368
desktop/scripts/get-tor.py Normal file
View file

@ -0,0 +1,368 @@
#!/usr/bin/env python3
import inspect
import os
from re import M
import sys
import hashlib
import shutil
import subprocess
import requests
import click
torbrowser_version = "11.0.14"
expected_win32_sha256 = (
"c14b979c81310ad039985e047dbb5b8058662bb3105b9022f7b9e0d18a29d0d6"
)
expected_win64_sha256 = (
"ced3de06d089fbbeb8cee309971ac26983aba8eaf948fedce472d40cdd572301"
)
expected_macos_sha256 = (
"558ae5ab188f62feb04c6b2e7f43eae2361e8ec1718e0f4f927801411d911e22"
)
expected_linux64_sha256 = (
"b606924fdf8237e697cf95c229189da5875c190875d729769655c7b67aeb9aa6"
)
win32_url = f"https://dist.torproject.org/torbrowser/{torbrowser_version}/torbrowser-install-{torbrowser_version}_en-US.exe"
win32_filename = f"torbrowser-install-{torbrowser_version}_en-US.exe"
win64_url = f"https://dist.torproject.org/torbrowser/{torbrowser_version}/torbrowser-install-win64-{torbrowser_version}_en-US.exe"
win64_filename = f"torbrowser-install-win64-{torbrowser_version}_en-US.exe"
macos_url = f"https://dist.torproject.org/torbrowser/{torbrowser_version}/TorBrowser-{torbrowser_version}-osx64_en-US.dmg"
macos_filename = f"TorBrowser-{torbrowser_version}-osx64_en-US.dmg"
linux64_url = f"https://dist.torproject.org/torbrowser/{torbrowser_version}/tor-browser-linux64-{torbrowser_version}_en-US.tar.xz"
linux64_filename = f"tor-browser-linux64-{torbrowser_version}_en-US.tar.xz"
# Common paths
root_path = os.path.dirname(
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
working_path = os.path.join(root_path, "build", "tor")
def get_tor_windows(platform):
if platform == "win32":
win_url = win32_url
win_filename = win32_filename
expected_win_sha256 = expected_win32_sha256
bin_filenames = [
"libcrypto-1_1.dll",
"libevent-2-1-7.dll",
"libevent_core-2-1-7.dll",
"libevent_extra-2-1-7.dll",
"libgcc_s_dw2-1.dll",
"libssl-1_1.dll",
"libssp-0.dll",
"libwinpthread-1.dll",
"tor.exe",
"zlib1.dll",
]
elif platform == "win64":
win_url = win64_url
win_filename = win64_filename
expected_win_sha256 = expected_win64_sha256
bin_filenames = [
"libcrypto-1_1-x64.dll",
"libevent-2-1-7.dll",
"libevent_core-2-1-7.dll",
"libevent_extra-2-1-7.dll",
"libgcc_s_seh-1.dll",
"libssl-1_1-x64.dll",
"libssp-0.dll",
"libwinpthread-1.dll",
"tor.exe",
"zlib1.dll",
]
else:
click.echo("invalid platform")
return
# Build paths
win_path = os.path.join(working_path, win_filename)
dist_path = os.path.join(root_path, "onionshare", "resources", "tor")
# Make sure the working folder exists
if not os.path.exists(working_path):
os.makedirs(working_path)
# Make sure Tor Browser is downloaded
if not os.path.exists(win_path):
print("Downloading {}".format(win_url))
r = requests.get(win_url)
open(win_path, "wb").write(r.content)
win_sha256 = hashlib.sha256(r.content).hexdigest()
else:
print("Already downloaded: {}".format(win_path))
win_data = open(win_path, "rb").read()
win_sha256 = hashlib.sha256(win_data).hexdigest()
# Compare the hash
if win_sha256 != expected_win_sha256:
print("ERROR! The sha256 doesn't match:")
print("expected: {}".format(expected_win32_sha256))
print(" actual: {}".format(win_sha256))
sys.exit(-1)
# Extract the bits we need from the exe
subprocess.Popen(
[
"7z",
"e",
"-y",
win_path,
"Browser\\TorBrowser\\Tor",
"-o%s" % os.path.join(working_path, "Tor"),
]
).wait()
subprocess.Popen(
[
"7z",
"e",
"-y",
win_path,
"Browser\\TorBrowser\\Data\\Tor\\geoip*",
"-o%s" % os.path.join(working_path, "Data"),
]
).wait()
# Copy into the onionshare resources
if os.path.exists(dist_path):
shutil.rmtree(dist_path)
os.makedirs(dist_path)
for filename in bin_filenames:
shutil.copyfile(
os.path.join(working_path, "Tor", filename),
os.path.join(dist_path, filename),
)
for filename in ["geoip", "geoip6"]:
shutil.copyfile(
os.path.join(working_path, "Data", filename),
os.path.join(dist_path, filename),
)
# Fetch the built-in bridges
update_tor_bridges()
def get_tor_macos():
# Build paths
dmg_tor_path = os.path.join(
"/Volumes", "Tor Browser", "Tor Browser.app", "Contents"
)
dmg_path = os.path.join(working_path, macos_filename)
dist_path = os.path.join(root_path, "onionshare", "resources", "tor")
if not os.path.exists(dist_path):
os.makedirs(dist_path, exist_ok=True)
# Make sure the working folder exists
if not os.path.exists(working_path):
os.makedirs(working_path)
# Make sure the zip is downloaded
if not os.path.exists(dmg_path):
print("Downloading {}".format(macos_url))
r = requests.get(macos_url)
open(dmg_path, "wb").write(r.content)
dmg_sha256 = hashlib.sha256(r.content).hexdigest()
else:
dmg_data = open(dmg_path, "rb").read()
dmg_sha256 = hashlib.sha256(dmg_data).hexdigest()
# Compare the hash
if dmg_sha256 != expected_macos_sha256:
print("ERROR! The sha256 doesn't match:")
print("expected: {}".format(expected_macos_sha256))
print(" actual: {}".format(dmg_sha256))
sys.exit(-1)
# Mount the dmg, copy data to the working path
subprocess.call(["hdiutil", "attach", dmg_path])
# Copy into dist
shutil.copyfile(
os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "geoip"),
os.path.join(dist_path, "geoip"),
)
shutil.copyfile(
os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "geoip6"),
os.path.join(dist_path, "geoip6"),
)
shutil.copyfile(
os.path.join(dmg_tor_path, "MacOS", "Tor", "tor.real"),
os.path.join(dist_path, "tor"),
)
os.chmod(os.path.join(dist_path, "tor"), 0o755)
shutil.copyfile(
os.path.join(dmg_tor_path, "MacOS", "Tor", "libevent-2.1.7.dylib"),
os.path.join(dist_path, "libevent-2.1.7.dylib"),
)
# obfs4proxy binary
shutil.copyfile(
os.path.join(dmg_tor_path, "MacOS", "Tor", "PluggableTransports", "obfs4proxy"),
os.path.join(dist_path, "obfs4proxy"),
)
os.chmod(os.path.join(dist_path, "obfs4proxy"), 0o755)
# snowflake-client binary
shutil.copyfile(
os.path.join(
dmg_tor_path, "MacOS", "Tor", "PluggableTransports", "snowflake-client"
),
os.path.join(dist_path, "snowflake-client"),
)
os.chmod(os.path.join(dist_path, "snowflake-client"), 0o755)
# Eject dmg
subprocess.call(["diskutil", "eject", "/Volumes/Tor Browser"])
# Fetch the built-in bridges
update_tor_bridges()
def get_tor_linux64():
# Build paths
tarball_path = os.path.join(working_path, linux64_filename)
dist_path = os.path.join(root_path, "onionshare", "resources", "tor")
# Make sure dirs exist
if not os.path.exists(working_path):
os.makedirs(working_path, exist_ok=True)
if not os.path.exists(dist_path):
os.makedirs(dist_path, exist_ok=True)
# Make sure the tarball is downloaded
if not os.path.exists(tarball_path):
print("Downloading {}".format(linux64_url))
r = requests.get(linux64_url)
open(tarball_path, "wb").write(r.content)
tarball_sha256 = hashlib.sha256(r.content).hexdigest()
else:
tarball_data = open(tarball_path, "rb").read()
tarball_sha256 = hashlib.sha256(tarball_data).hexdigest()
# Compare the hash
if tarball_sha256 != expected_linux64_sha256:
print("ERROR! The sha256 doesn't match:")
print("expected: {}".format(expected_linux64_sha256))
print(" actual: {}".format(tarball_sha256))
sys.exit(-1)
# Delete extracted tarball, if it's there
shutil.rmtree(os.path.join(working_path, "tor-browser_en-US"), ignore_errors=True)
# Extract the tarball
subprocess.call(["tar", "-xvf", tarball_path], cwd=working_path)
tarball_tor_path = os.path.join(
working_path, "tor-browser_en-US", "Browser", "TorBrowser"
)
# Copy into dist
shutil.copyfile(
os.path.join(tarball_tor_path, "Data", "Tor", "geoip"),
os.path.join(dist_path, "geoip"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Data", "Tor", "geoip6"),
os.path.join(dist_path, "geoip6"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "tor"),
os.path.join(dist_path, "tor"),
)
os.chmod(os.path.join(dist_path, "tor"), 0o755)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libcrypto.so.1.1"),
os.path.join(dist_path, "libcrypto.so.1.1"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libevent-2.1.so.7"),
os.path.join(dist_path, "libevent-2.1.so.7"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libssl.so.1.1"),
os.path.join(dist_path, "libssl.so.1.1"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libstdc++", "libstdc++.so.6"),
os.path.join(dist_path, "libstdc++.so.6"),
)
print(f"Tor binaries extracted to: {dist_path}")
# Fetch the built-in bridges
update_tor_bridges()
def update_tor_bridges():
"""
Update the built-in Tor Bridges in OnionShare's torrc templates.
"""
torrc_template_dir = os.path.join(
root_path, os.pardir, "cli/onionshare_cli/resources"
)
endpoint = "https://bridges.torproject.org/moat/circumvention/builtin"
r = requests.post(
endpoint,
headers={"Content-Type": "application/vnd.api+json"},
)
if r.status_code != 200:
print(
f"There was a problem fetching the latest built-in bridges: status_code={r.status_code}"
)
return False
result = r.json()
if "errors" in result:
print(
f"There was a problem fetching the latest built-in bridges: errors={result['errors']}"
)
return False
for bridge_type in ["meek-azure", "obfs4", "snowflake"]:
if result[bridge_type]:
if bridge_type == "meek-azure":
torrc_template_extension = "meek_lite_azure"
else:
torrc_template_extension = bridge_type
torrc_template = os.path.join(
root_path,
torrc_template_dir,
f"torrc_template-{torrc_template_extension}",
)
with open(torrc_template, "w") as f:
f.write(f"# Enable built-in {bridge_type} bridge\n")
bridges = result[bridge_type]
# Sorts the bridges numerically by IP, since they come back in
# random order from the API each time, and create noisy git diff.
bridges.sort(key=lambda s: s.split()[1])
for item in bridges:
f.write(f"Bridge {item}\n")
@click.command()
@click.argument("platform")
def main(platform):
"""
Download Tor Browser and extract tor binaries
"""
valid_platforms = ["win32", "win64", "macos", "linux64"]
if platform not in valid_platforms:
click.echo(f"platform must be one of: {valid_platforms}")
return
if platform == "win32":
get_tor_windows(platform)
elif platform == "win64":
get_tor_windows(platform)
elif platform == "macos":
get_tor_macos()
elif platform == "linux64":
get_tor_linux64()
else:
click.echo("invalid platform")
if __name__ == "__main__":
main()

View file

@ -24,6 +24,7 @@ import platform
import shutil
import cx_Freeze
from cx_Freeze import setup, Executable
from setuptools import find_packages
# There's an obscure cx_Freeze bug that I'm hitting that's preventing the macOS
# package from getting built. This is some monkeypatching to fix it.
@ -107,7 +108,10 @@ if platform.system() == "Darwin" or platform.system() == "Linux":
# Discover the version
with open(os.path.join("..", "cli", "onionshare_cli", "resources", "version.txt")) as f:
version = f.read().strip()
# change a version like 2.6.dev1 to just 2.6, for cx_Freeze's sake
last_digit = version[-1]
if version.endswith(f".dev{last_digit}"):
version = version[0:-5]
# Build
include_files = [(os.path.join("..", "LICENSE"), "LICENSE")]
@ -148,6 +152,11 @@ setup(
name="onionshare",
version=version,
description="Securely and anonymously share files, host websites, and chat with friends using the Tor network",
packages=find_packages(
where=".",
include=["onionshare"],
exclude=["package", "screenshots", "scripts", "tests"],
),
options={
# build_exe, for Windows and macOS
"build_exe": {
@ -214,7 +223,7 @@ setup(
# bdist_mac, making the macOS app bundle
"bdist_mac": {
"iconfile": os.path.join("onionshare", "resources", "onionshare.icns"),
"bundle_name": "OnionShare"
"bundle_name": "OnionShare",
},
},
executables=[

View file

@ -3,7 +3,7 @@
import setuptools
# The version must be hard-coded because Snapcraft won't have access to ../cli
version = "2.5"
version = "2.6"
setuptools.setup(
name="onionshare",

View file

@ -1,6 +1,6 @@
project = "OnionShare"
author = copyright = "Micah Lee, et al."
version = release = "2.5"
version = release = "2.6"
extensions = ["sphinx_rtd_theme"]
templates_path = ["_templates"]

View file

@ -1,6 +1,6 @@
name: onionshare
base: core18
version: '2.5'
version: "2.6"
summary: Securely and anonymously share files, host websites, and chat using Tor
description: |
OnionShare lets you securely and anonymously send and receive files. It works by starting
@ -15,7 +15,7 @@ apps:
onionshare:
common-id: org.onionshare.OnionShare
command: onionshare
extensions: [ gnome-3-34 ]
extensions: [gnome-3-34]
plugs:
- desktop
- home