Skip to main content

freezer, installer and updater for Python applications

Project description

PyShip

CI codecov

Enables shipping a python application to end users.

PyShip's Major Features

  • Freeze practically any Python application.
  • Optional Code Signing (avoid Windows "Unknown Publisher" warning). This is not required, and you can ship your application for free, but code signing is supported if you have a Code Signing certificate (typically an additional cost).
  • Installs via Microsoft Windows App Store or self-hosted installers entirely outside the app store.
  • Automatic application updating in the background (no user intervention).
  • OS native application (e.g., .exe for Windows).
  • Run on OS startup option.

Currently only for Windows. May be extended to other Operating Systems in the future.

Configuration

pyship settings are configured in your project's pyproject.toml under [tool.pyship].

pyproject.toml

[tool.pyship]
ui = "cli"               # "cli" (default), "tui", or "gui"
run_on_startup = false   # true to run the app on OS startup (default: false)
code_sign = false        # true to enable code signing (default: false)

UI Modes

Value Description Python interpreter
"cli" Standard command-line application. Output streams directly to the console. python.exe
"tui" Text User Interface (e.g. textual, curses, prompt_toolkit). Unbuffered I/O with direct console access. python.exe
"gui" Graphical application (e.g. PyQt, tkinter). Runs without a console window; output captured for diagnostics. pythonw.exe

Legacy: is_gui = true is still accepted and maps to ui = "gui", but emits a deprecation warning.

AWS Keys in CLI Arguments

The user may optionally provide their AWS keys as CLI arguments. They may also be provided in the ~/.aws/credentials file (typical for AWS CLI and boto3). These are not in pyproject.toml since it is usually checked into the code repo.

Argument Description
-i, --id AWS Access Key ID
-s, --secret AWS Secret Access Key

Build Outputs

A ship() run produces:

  • installers/{app_name}_installer_win64.exe — the NSIS installer for direct distribution. The platform suffix (win64 on 64-bit Windows) identifies the target OS and architecture, and is the same for all installer types.
  • {app_name}_{version}.clip — the zipped CLIP that pyshipupdate downloads for background updates. It is only created when uploading to S3, and it is never packed into the NSIS installer (which would roughly double the installer's size).

If your project has a LICENSE file, it is displayed on the installer's license page. pyship normalizes the file's line endings to CRLF in a build-tree copy automatically (NSIS requires DOS-format text files), so the file in your repository can stay LF-only.

The pyship command exits with status 1 if no installer was produced (for example, when code signing is blocked because the build is running over an RDP session), so build scripts fail loudly instead of appearing to succeed.

Microsoft Windows Code Signing (Optional)

Signing your executables suppresses the Windows SmartScreen "Unknown Publisher" warning. pyship signs two files: the launcher stub ({app_name}.exe) and the NSIS installer ({app_name}_installer_*.exe). The launcher is signed before NSIS packages it, so the signed binary ends up inside the installer.

pyship supports two signing modes:

  • PFX file — a .pfx certificate file with a password (traditional software certificates)
  • Hardware token — a USB security key (e.g. Sectigo OV, SafeNet eToken, YubiKey) whose certificate is in the Windows Certificate Store

You need signtool.exe from the Windows SDK for either mode.

While Code Signing is fairly common, it does involve a few steps. The instructions below should help, and pyship will try to help you through the process by flagging any issues it sees.

Warning: Accessing a computer over RDP (Remote Desktop) usually does not work for entering in the certificate "PIN". It will likely fail and the error messages will appear as if the PIN you entered is incorrect. This is a major issue since most token devices allow only a small number of failed attempts (typically 3 to 5) before the device is locked. If you have a token device that requires a PIN, ensure you are directly accessing the computer via regular keyboard, mouse, and video.

Getting a code-signing certificate

An Authenticode certificate identifies you (or your organization) as the publisher of the software. Windows uses it to suppress SmartScreen warnings and to display your name in UAC prompts and Add/Remove Programs.

Certificate types

Type SmartScreen behaviour Typical cost Notes
OV (Organisation Validation) Builds reputation over time; new certs still trigger SmartScreen until enough installs accumulate ~$200–400/yr Issued to a verified company or individual. Most common for open-source and small commercial projects.
EV (Extended Validation) Immediate SmartScreen reputation from the first install ~$400–600/yr ONLY for registered organizations (Corporations). NOT for individuals. Requires a hardware token (USB) or cloud HSM. Strongest trust signal.

Where to buy

tldr:

Individuals, ~$300-400/year: Sectigo Code Signing Certificate .

Existing organizations (registered LLCs, Corporations, etc.), ~$400-500/year: Sectigo EV Code Signing Certificate .

Other options

Certificates are issued by Certificate Authorities (CAs). Common options include:

  • SSL.com — offers OV and EV code-signing certificates; EV certificates can be stored on their cloud-based eSigner HSM, making them usable in CI without a physical USB token.
  • DigiCert — popular for EV certificates; offers KeyLocker (cloud HSM) for CI integration.
  • Sectigo (formerly Comodo) — widely used OV and EV code-signing certificates.
  • SignPath — free certificates for open-source projects via their Foundation programme.

The purchase process typically involves:

  1. Choose a CA and certificate type (OV or EV).
  2. Complete identity verification (business or personal registration documents for OV; additional legal vetting for EV).
  3. The CA issues a .pfx (PKCS #12) file containing your private key and certificate chain, or provides access via a hardware token / cloud HSM.

Verification usually takes 1–5 business days for OV, and 1–2 weeks for EV.

The PFX file and password

The .pfx file (also called .p12) is an encrypted archive that bundles your private signing key with the certificate. The PFX password (also called the export password) protects this file — anyone with both the file and the password can sign software as you.

Creating a PFX from separate files

If your CA provided a .crt certificate and a .key private key separately (common with some CAs), combine them into a PFX:

openssl pkcs12 -export -out certificate.pfx -inkey private.key -in certificate.crt -certfile ca_chain.crt

OpenSSL will prompt you to set a password — this becomes your PFX password.

Extracting certificate info from a PFX

To view the certificate subject (needed for msix_publisher):

certutil -dump certificate.pfx

Security best practices

  • Never commit the .pfx file to your source repository. Add *.pfx to .gitignore.
  • Store the PFX password in a secrets manager or CI secrets — not in source code, environment files, or scripts.
  • For CI, base64-encode the PFX file and store it as a CI secret, then decode it at build time:
    - name: Decode signing certificate
      run: echo "${{ secrets.PFX_BASE64 }}" | base64 --decode > certificate.pfx
    - name: Ship
      env:
        PYSHIP_SIGNING_CERTIFICATE_PIN: ${{ secrets.PFX_PASSWORD }}
      run: python ship.py
    
  • Rotate certificates before expiry. Most code-signing certificates are valid for 1–3 years.

Getting signtool.exe

Install the Windows SDK and select Windows SDK Signing Tools for Desktop Apps. pyship auto-discovers the newest version under C:\Program Files (x86)\Windows Kits\10\bin\.

Enabling code signing

Set code_sign=True to enable signing. pyship runs a pre-flight check (verifies the PFX file exists or the hardware token is plugged in) and raises PyshipSigningUnavailable if signing infrastructure is missing or if any file fails to sign. When code_sign=False (the default), signing is skipped entirely.

PFX basic usage

from pathlib import Path
from pyship import PyShip

ps = PyShip(
    project_dir=Path("path/to/your/app"),
    code_sign=True,
    pfx_path=Path("path/to/certificate.pfx"),
    certificate_password="your-pfx-password",
)
ps.ship()

CI / environment variable password

Avoid storing the PFX password in source code. Set the PYSHIP_SIGNING_CERTIFICATE_PIN environment variable instead — pyship reads it automatically when certificate_password is not set:

ps = PyShip(
    project_dir=Path("path/to/your/app"),
    code_sign=True,
    pfx_path=Path("path/to/certificate.pfx"),
    # password read from PYSHIP_SIGNING_CERTIFICATE_PIN env var at ship() time
)
ps.ship()

Set the secret in your CI system (e.g. GitHub Actions → Settings → Secrets) and expose it in your workflow:

- name: Ship
  env:
    PYSHIP_SIGNING_CERTIFICATE_PIN: ${{ secrets.PFX_PASSWORD }}
  run: python ship.py

Explicit signtool path

If signtool.exe is not in the default Windows SDK location, point pyship directly at it:

ps = PyShip(
    ...
    signtool_path=Path(r"C:\custom\path\signtool.exe"),
)

Timestamp server

By default pyship uses DigiCert's RFC 3161 server (http://timestamp.digicert.com). Override it with the timestamp_url field:

ps = PyShip(
    ...
    timestamp_url="http://timestamp.sectigo.com",
)

Verifying a signed executable

signtool verify /pa /v YourApp.exe

Skipping signing

Leave code_sign as False (the default). pyship will build and package the executables without signing them.

Hardware Token Signing

If your code-signing certificate lives on a USB hardware token (smart card), pyship can sign directly from the Windows Certificate Store instead of a PFX file. This is common for EV certificates and newer OV certificates from CAs like Sectigo, DigiCert, and SSL.com.

When the token is plugged in and its middleware is installed, the certificate appears in the Windows Certificate Store. pyship performs a pre-flight check — it verifies the hardware is present (via Windows PnP device enumeration) and that the certificate is in the store before attempting to sign.

Selecting the certificate

Provide exactly one of these to identify which certificate to use:

Parameter signtool flag Description
certificate_sha1 /sha1 SHA1 thumbprint (40 hex chars) — most precise
certificate_subject /n Certificate subject name — matches by name
certificate_auto_select /a Auto-select the best signing certificate

Finding the SHA1 thumbprint

certutil -user -store My

Look for the Cert Hash(sha1): line of your code-signing certificate and copy the 40-character hex value.

Basic hardware token usage

from pathlib import Path
from pyship import PyShip

ps = PyShip(
    project_dir=Path("path/to/your/app"),
    code_sign=True,
    certificate_sha1="AABBCCDD1122334455667788AABBCCDD11223344",
)
ps.ship()

The token's middleware will prompt for the PIN interactively via its own dialog.

Providing the token PIN for automation

For CI or unattended builds, set the PYSHIP_SIGNING_CERTIFICATE_PIN environment variable. pyship reads it automatically:

# GitHub Actions example
- name: Ship
  env:
    PYSHIP_SIGNING_CERTIFICATE_PIN: ${{ secrets.TOKEN_PIN }}
  run: python ship.py

Or pass it directly (not recommended for CI):

ps = PyShip(
    code_sign=True,
    certificate_sha1="AABBCCDD1122334455667788AABBCCDD11223344",
    certificate_password="123456",  # PIN directly
)

Advanced: CSP and key container

Some tokens (e.g. SafeNet eToken) require specifying the Cryptographic Service Provider:

ps = PyShip(
    code_sign=True,
    certificate_sha1="AABBCCDD1122334455667788AABBCCDD11223344",
    certificate_csp="eToken Base Cryptographic Provider",
    certificate_key_container="my-container",
)

Microsoft Store Distribution (MSIX) (Optional)

The Microsoft Store is an alternative distribution channel that gives users a trusted, one-click install experience and eliminates SmartScreen warnings entirely. pyship can produce both a traditional NSIS installer and an MSIX package in a single ship() call.

Prerequisites

Requirement Notes
Windows SDK Provides makeappx.exe and signtool.exe. Install from developer.microsoft.com/windows/downloads/windows-sdk and select Windows SDK Signing Tools for Desktop Apps. pyship auto-discovers both tools.
Authenticode certificate Same PFX used for code signing. The Publisher field in the MSIX manifest must exactly match the certificate subject. Self-signed certificates work for sideloading and testing but not for Store submission.
Microsoft Partner Center account Required for Store submission only. Register at partner.microsoft.com. One-time $19 fee for individuals.

Basic usage

Set msix=True and supply the certificate subject DN as msix_publisher:

from pathlib import Path
from pyship import PyShip

ps = PyShip(
    project_dir=Path("path/to/your/app"),
    code_sign=True,
    pfx_path=Path("certificate.pfx"),
    # password read from PYSHIP_SIGNING_CERTIFICATE_PIN env var
    msix=True,
    msix_publisher="CN=My Company, O=My Company LLC, C=US",  # must match cert subject exactly
)
ps.ship()

This produces two files in installers/:

  • {app_name}_installer_win64.exe — the NSIS installer (direct distribution, auto-updates via pyshipupdate)
  • {app_name}_installer_win64.msix — the MSIX package (Store or sideloading, updates managed by the Store)

Both are signed with the same certificate. The NSIS installer is built first; the MSIX is packed from the same app directory afterwards, so they do not interfere with each other.

Finding the msix_publisher string

msix_publisher must be the exact Distinguished Name of your signing certificate's subject. To read it from your PFX:

certutil -dump certificate.pfx

Look for the Subject: line, e.g. CN=My Company, O=My Company LLC, C=US. Copy it verbatim — any difference will cause installation to fail.

Store logo assets

MSIX packages require three PNG logo files. pyship generates 1×1 white placeholder PNGs automatically, which is sufficient for testing and sideloading. For Store submission, provide real assets at the correct sizes:

File Size
StoreLogo.png 50×50
Square44x44Logo.png 44×44
Square150x150Logo.png 150×150

Point pyship at a directory containing these files with store_assets_dir:

ps = PyShip(
    ...
    msix=True,
    msix_publisher="CN=My Company, O=My Company LLC, C=US",
    store_assets_dir=Path("store_assets"),
)

Any asset not found in that directory falls back to the placeholder PNG automatically.

Explicit makeappx path

If makeappx.exe is not in the default Windows SDK location:

ps = PyShip(
    ...
    makeappx_path=Path(r"C:\custom\path\makeappx.exe"),
)

Sideloading (without the Store)

Signed MSIX packages can be installed directly without going through the Store — double-click the .msix file or use:

Add-AppxPackage -Path "{app_name}_installer_win64.msix"

This is useful for enterprise deployments and beta distribution.

Submitting to the Microsoft Store

  1. Log in to Partner Center.
  2. Go to Windows & XboxOverviewCreate a new app and reserve your app name.
  3. Under SubmissionsNew submission, complete the store listing (description, screenshots, age rating, pricing).
  4. Upload your signed .msix from the installers/ directory under Packages.
  5. Submit for certification. Review typically takes 1–3 business days.

Limitations

  • run_on_startup = true is not supported in MSIX builds. The MSIX runtime requires a StartupTask extension in the manifest; pyship logs a warning and continues, but the app will not auto-start. Configure startup behaviour manually in a custom manifest if needed.
  • MSIX packages installed via the Store are updated by the Store, not by pyshipupdate. The self-update logic in pyshipupdate is silently inactive when running inside an MSIX container.

NSIS vs. MSIX comparison

NSIS installer MSIX
Distribution Your own URL / S3 Microsoft Store or sideload
SmartScreen Suppressed with EV cert Not shown (implicitly trusted)
Auto-update pyshipupdate Store manages updates
Review required No Yes (Store only; ~1–3 days)
Revenue share None 15% (>$1M/yr: 12%)
Startup support Yes Requires manifest extension

Testing

Run tests with:

venv\Scripts\python.exe -m pytest test_pyship/ -v

Environment Variables

Variable Description Default
PYSHIP_SIGNING_CERTIFICATE_PIN PFX password or hardware token PIN for code signing. Read automatically by ship() when certificate_password is not set. (not set)
AWSIMPLE_USE_MOTO_MOCK Set to 0 to use real AWS instead of moto mock. Required for test_f_update which tests cross-process S3 updates. Requires valid AWS credentials configured. 1 (use moto)
MAKE_NSIS_PATH Path to the NSIS makensis.exe executable. C:\Program Files (x86)\NSIS\makensis.exe

Test Modes

  • Default (moto mock): All tests run with mocked AWS S3. No credentials needed. test_f_update is skipped.
  • Real AWS (AWSIMPLE_USE_MOTO_MOCK=0): All tests run against real AWS S3. test_f_update runs and tests cross-process updates.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyship-0.7.5-py3-none-any.whl (66.8 kB view details)

Uploaded Python 3

File details

Details for the file pyship-0.7.5-py3-none-any.whl.

File metadata

  • Download URL: pyship-0.7.5-py3-none-any.whl
  • Upload date:
  • Size: 66.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for pyship-0.7.5-py3-none-any.whl
Algorithm Hash digest
SHA256 539db5c9c6aa2cb83dd935f98b7eacd883cad6519a50a4066a872f76318add65
MD5 593af2a130966976669eda6a498cb12a
BLAKE2b-256 ec4d195e7e4a44134e7cad54f35e995ad8cf9f2dd75ca0d221e0bcb2705cd73c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page