From 537fe67959598fe0f92dd9c5361262aaa7c41971 Mon Sep 17 00:00:00 2001 From: riteshr19 Date: Fri, 20 Feb 2026 20:24:16 -0600 Subject: [PATCH 1/5] feat: add PyInstaller CI binaries and Streamlit web UI (closes #44, closes #45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - feat(ci): add .github/workflows/build-binaries.yml — GitHub Actions pipeline using PyInstaller to build standalone executables for Linux (x86_64), macOS (arm64) and Windows (x64) on every tag push; binaries are uploaded as artifacts and attached to GitHub Releases - feat(ux): add app/streamlit_app.py — a local Streamlit web UI wrapper that lets users upload a PDF, set a password with confirmation, and download the encrypted file without any CLI knowledge - feat: add requirements-webui.txt with streamlit dependency (kept separate from core dependencies to keep the CLI install lean) - docs: update README with Web UI usage instructions, pre-built binary download section, and fix existing typos --- .github/workflows/build-binaries.yml | 95 +++++++++++++++++++++ README.md | 26 +++++- app/streamlit_app.py | 118 +++++++++++++++++++++++++++ requirements-webui.txt | 3 + uv.lock | 3 + 5 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/build-binaries.yml create mode 100644 app/streamlit_app.py create mode 100644 requirements-webui.txt create mode 100644 uv.lock diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml new file mode 100644 index 0000000..9e9a2e7 --- /dev/null +++ b/.github/workflows/build-binaries.yml @@ -0,0 +1,95 @@ +name: Build Native Binaries + +on: + push: + tags: + - "v*" + workflow_dispatch: + +jobs: + build: + name: Build (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + artifact_name: passifypdf-linux-x86_64 + binary_path: dist/passifypdf + - os: macos-latest + artifact_name: passifypdf-macos-arm64 + binary_path: dist/passifypdf + - os: windows-latest + artifact_name: passifypdf-windows-x86_64 + binary_path: dist/passifypdf.exe + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Install dependencies + run: uv sync + + - name: Install PyInstaller + run: uv pip install pyinstaller + + - name: Build binary + run: | + uv run pyinstaller \ + --onefile \ + --name passifypdf \ + --console \ + passifypdf/encryptpdf.py + + - name: Smoke test (Unix) + if: runner.os != 'Windows' + run: | + ./dist/passifypdf --help + + - name: Smoke test (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + .\dist\passifypdf.exe --help + + - name: Upload binary artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact_name }} + path: ${{ matrix.binary_path }} + if-no-files-found: error + + release: + name: Attach Binaries to Release + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: dist/ + + - name: List downloaded artifacts + run: ls -R dist/ + + - name: Create release and upload assets + uses: softprops/action-gh-release@v2 + with: + files: | + dist/passifypdf-linux-x86_64/passifypdf + dist/passifypdf-macos-arm64/passifypdf + dist/passifypdf-windows-x86_64/passifypdf.exe + generate_release_notes: true diff --git a/README.md b/README.md index 1c96951..8f108a7 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,27 @@ passifypdf -i input.pdf -o protected.pdf -p mySecretPassword ``` ## Known Issues -If you have nay special chars(example: an emoji like Star 🌟) in the PDF file, it gives a minor complain during execution. -But it still does the job, so you can ingore that "char or object error" which you see in the output. +If you have any special chars (example: an emoji like Star 🌟) in the PDF file, it gives a minor complaint during execution. +But it still does the job, so you can ignore that "char or object error" which you see in the output. + +## Web UI (Streamlit) + +A local web interface is available for users who prefer a graphical workflow: + +```bash +# Install web UI dependencies +pip install -r requirements-webui.txt + +# Launch the app +streamlit run app/streamlit_app.py +``` + +Open the URL shown in your terminal (usually `http://localhost:8501`), upload a PDF, enter a password and download the protected file — no command line needed. + +## Download Pre-built Binaries + +Standalone executables for Linux, macOS, and Windows are built automatically on every tagged release via GitHub Actions. Visit the [Releases page](https://github.com/SUPAIDEAS/passifypdf/releases) to download the binary for your platform — no Python installation required. ## Note: -In general you can use passifypdf to protect your PDF files against chance attackers. -But you should not rely on this for mission-critical data or situation. +In general you can use passifypdf to protect your PDF files against chance attackers. +But you should not rely on this for mission-critical data or situations. diff --git a/app/streamlit_app.py b/app/streamlit_app.py new file mode 100644 index 0000000..2a83817 --- /dev/null +++ b/app/streamlit_app.py @@ -0,0 +1,118 @@ +"""Streamlit web UI wrapper for passifypdf. + +Run locally with: + streamlit run app/streamlit_app.py +""" + +import io +import tempfile +from pathlib import Path + +import streamlit as st + +from passifypdf.encryptpdf import encrypt_pdf + + +# ── Page config ──────────────────────────────────────────────────────────── + +st.set_page_config( + page_title="passifypdf — PDF Password Protector", + page_icon="🔒", + layout="centered", + initial_sidebar_state="collapsed", +) + +# ── Title ────────────────────────────────────────────────────────────────── + +st.title("🔒 passifypdf") +st.subheader("Protect your PDF files with a password — right in the browser.") +st.markdown("---") + +# ── File upload ──────────────────────────────────────────────────────────── + +uploaded_file = st.file_uploader( + "Upload a PDF file", + type=["pdf"], + help="Select the PDF you want to encrypt.", +) + +# ── Password ─────────────────────────────────────────────────────────────── + +col1, col2 = st.columns(2) +with col1: + password = st.text_input( + "Password", + type="password", + placeholder="Enter a strong password", + help="This password will be required to open the encrypted PDF.", + ) +with col2: + confirm_password = st.text_input( + "Confirm password", + type="password", + placeholder="Re-enter the password", + ) + +# ── Output filename ──────────────────────────────────────────────────────── + +output_name = st.text_input( + "Output filename", + value="protected.pdf", + help="Name of the encrypted file you will download.", +) +if not output_name.lower().endswith(".pdf"): + output_name += ".pdf" + +# ── Encrypt button ───────────────────────────────────────────────────────── + +if st.button("🔐 Encrypt PDF", type="primary", use_container_width=True): + if not uploaded_file: + st.error("Please upload a PDF file first.") + elif not password: + st.error("Please enter a password.") + elif password != confirm_password: + st.error("Passwords do not match. Please try again.") + else: + with st.spinner("Encrypting your PDF…"): + try: + # Write uploaded bytes to a temp input file + with tempfile.NamedTemporaryFile( + suffix=".pdf", delete=False + ) as tmp_in: + tmp_in.write(uploaded_file.read()) + tmp_in_path = Path(tmp_in.name) + + # Encrypt to a temp output file + with tempfile.NamedTemporaryFile( + suffix=".pdf", delete=False + ) as tmp_out: + tmp_out_path = Path(tmp_out.name) + + encrypt_pdf(tmp_in_path, tmp_out_path, password) + + # Read the encrypted bytes and offer download + encrypted_bytes = tmp_out_path.read_bytes() + + st.success("✅ PDF encrypted successfully!") + st.download_button( + label="⬇️ Download encrypted PDF", + data=encrypted_bytes, + file_name=output_name, + mime="application/pdf", + use_container_width=True, + ) + + except Exception as exc: + st.error(f"❌ Encryption failed: {exc}") + finally: + # Clean up temp files + tmp_in_path.unlink(missing_ok=True) + tmp_out_path.unlink(missing_ok=True) + +# ── Footer ───────────────────────────────────────────────────────────────── + +st.markdown("---") +st.caption( + "passifypdf — open source PDF encryption tool. " + "[View on GitHub](https://github.com/SUPAIDEAS/passifypdf)" +) diff --git a/requirements-webui.txt b/requirements-webui.txt new file mode 100644 index 0000000..db68f68 --- /dev/null +++ b/requirements-webui.txt @@ -0,0 +1,3 @@ +# Requirements for the optional Streamlit web UI (app/streamlit_app.py) +# Install with: pip install -r requirements-webui.txt +streamlit>=1.35.0 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..bda0207 --- /dev/null +++ b/uv.lock @@ -0,0 +1,3 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" From 1567a997f210035c8d3ebe9962e6a06a6e562179 Mon Sep 17 00:00:00 2001 From: NirmalChandra Date: Mon, 23 Feb 2026 14:54:36 +0530 Subject: [PATCH 2/5] Add UI launch instructions to README Added instructions for launching the UI and running commands. --- UI-README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 UI-README.md diff --git a/UI-README.md b/UI-README.md new file mode 100644 index 0000000..3c94f5e --- /dev/null +++ b/UI-README.md @@ -0,0 +1,16 @@ +Use the following CLI commands to launch the UI. +- UI allows you to drag-drop PDF files and download them + +Commands to run: +```shell +uv sync --all-groups +uv pip install -r requirements-webui.txt +uv pip install -e . +uv run streamlit run app/streamlit_app.py +``` +Then, +Open: +http://localhost:8501/ + +Screenshot: +image From 59d14b142ecb561475e4e5fbfa688990454cdb79 Mon Sep 17 00:00:00 2001 From: NirmalChandra Date: Mon, 23 Feb 2026 14:56:01 +0530 Subject: [PATCH 3/5] Enhance UI-README with encryption details Updated UI-README to clarify PDF file functionality. --- UI-README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI-README.md b/UI-README.md index 3c94f5e..7266485 100644 --- a/UI-README.md +++ b/UI-README.md @@ -1,5 +1,5 @@ Use the following CLI commands to launch the UI. -- UI allows you to drag-drop PDF files and download them +- UI allows you to drag-drop PDF file, encrypt it(password protect it) and then, download it. Commands to run: ```shell From 9f2b734fc1a79b949f16d08726b31f0ed3a4ddc9 Mon Sep 17 00:00:00 2001 From: NirmalChandra Date: Mon, 23 Feb 2026 14:57:39 +0530 Subject: [PATCH 4/5] Add passifypdf to web UI requirements --- requirements-webui.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-webui.txt b/requirements-webui.txt index db68f68..bc9b4e6 100644 --- a/requirements-webui.txt +++ b/requirements-webui.txt @@ -1,3 +1,4 @@ # Requirements for the optional Streamlit web UI (app/streamlit_app.py) # Install with: pip install -r requirements-webui.txt streamlit>=1.35.0 +passifypdf From efa4e17a2fbc5d8ee52dd2a3ecdd4ed803bd2eca Mon Sep 17 00:00:00 2001 From: NirmalChandra Date: Mon, 23 Feb 2026 15:02:18 +0530 Subject: [PATCH 5/5] Update UI-README.md --- UI-README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI-README.md b/UI-README.md index 7266485..05c5e63 100644 --- a/UI-README.md +++ b/UI-README.md @@ -5,7 +5,7 @@ Commands to run: ```shell uv sync --all-groups uv pip install -r requirements-webui.txt -uv pip install -e . +uv pip install -e . <---------- Important, otherwise it gives import error. uv run streamlit run app/streamlit_app.py ``` Then,