Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ permissions:

jobs:
pytest:
# Windows, because that is what PassMan ships for: the paths, the
# Windows, because that is what SecretSpace ships for: the paths, the
# installer marker and the tkinter build all match what users run.
runs-on: windows-latest
strategy:
Expand Down
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ data/
*.vault.enc.base
config.json

# What the PassMan Builder writes here: the installer it packs from
# installer\PassMan.iss, and the builder itself (py builder\build.py
# What the SecretSpace Builder writes here: the installer it packs from
# installer\SecretSpace.iss, and the builder itself (py builder\build.py
# --builder). Publish them as release assets rather than committing them.
# PassMan.exe is the portable exe of 2.2 and earlier, no longer built; still
# ignored in case an old copy is around.
SecretSpace.exe
PassMan.exe
PassMan-Client-Installer.exe
PassMan-Builder.exe
SecretSpace-Client-Installer.exe
SecretSpace-Builder.exe

# Working notes, kept locally and out of the public repository.
TODO.txt
132 changes: 72 additions & 60 deletions README.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Security policy

PassMan keeps passwords, so security reports are welcome and taken seriously.
SecretSpace keeps passwords, so security reports are welcome and taken seriously.

## Reporting a vulnerability

Expand All @@ -23,5 +23,5 @@ Please give a reasonable time for a fix before you publish anything.

The client in this repository: vault encryption, the master password,
Secret Key and recovery phrase handling, the clipboard, the installer and the
sync client. Server issues go to the PassMan-Server-Windows or
PassMan-Server-Linux repository, the same way.
sync client. Server issues go to the SecretSpace-Server-Windows or
SecretSpace-Server-Linux repository, the same way.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# -*- mode: python ; coding: utf-8 -*-
#
# PassMan-Builder.exe: build.py + builder_gui.py frozen into one file that
# checks the prerequisites and builds PassMan. Built with:
# SecretSpace-Builder.exe: build.py + builder_gui.py frozen into one file that
# checks the prerequisites and builds SecretSpace. Built with:
# py builder\build.py --builder
# It borrows PassMan's theme and widgets from sourceFiles/ for its look.
# It borrows SecretSpace's theme and widgets from sourceFiles/ for its look.
import os
import sys

Expand All @@ -14,8 +14,8 @@ SRC = os.path.join(os.path.dirname(HERE), 'sourceFiles')
sys.path.insert(0, SRC)
from build_version import version_info # Properties > Details

datas = [(os.path.join(SRC, 'PassMan-logo.png'), '.'),
(os.path.join(SRC, 'PassMan-logo.ico'), '.')]
datas = [(os.path.join(SRC, 'SecretSpace-logo.png'), '.'),
(os.path.join(SRC, 'SecretSpace-logo.ico'), '.')]
binaries = []
hiddenimports = []
tmp_ret = collect_all('customtkinter')
Expand Down Expand Up @@ -43,7 +43,7 @@ exe = EXE(
a.binaries,
a.datas,
[],
name='PassMan-Builder',
name='SecretSpace-Builder',
debug=False,
bootloader_ignore_signals=False,
strip=False,
Expand All @@ -55,6 +55,6 @@ exe = EXE(
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=[os.path.join(SRC, 'PassMan-logo.ico')],
version=version_info("PassMan Builder", "PassMan-Builder.exe"),
icon=[os.path.join(SRC, 'SecretSpace-logo.ico')],
version=version_info("SecretSpace Builder", "SecretSpace-Builder.exe"),
)
71 changes: 37 additions & 34 deletions builder/build.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""
Build PassMan-Client-Installer.exe from the source, after checking every
Build SecretSpace-Client-Installer.exe from the source, after checking every
prerequisite. This replaces build.bat. The sync server is built from its own
repository (PassMan-Server-Windows).
repository (SecretSpace-Server-Windows).

Two ways to run it:

PassMan-Builder.exe double-click; a window walks through it
SecretSpace-Builder.exe double-click; a window walks through it
py builder\\build.py the same steps in a terminal

PassMan-Builder.exe is this module frozen with builder_gui.py. It does not
SecretSpace-Builder.exe is this module frozen with builder_gui.py. It does not
compile anything itself -- a frozen exe cannot run PyInstaller on the
source -- it drives a real Python installed on the PC, the way build.bat
did. Everything here is plain functions over an injectable command runner,
Expand All @@ -34,10 +34,10 @@
"requirements.txt",
os.path.join("sourceFiles", "gui.py"),
os.path.join("sourceFiles", "version.py"),
os.path.join("sourceFiles", "PassMan-installer.spec"),
os.path.join("sourceFiles", "PassMan-logo.png"),
os.path.join("sourceFiles", "PassMan-logo.ico"),
os.path.join("installer", "PassMan.iss"),
os.path.join("sourceFiles", "SecretSpace-installer.spec"),
os.path.join("sourceFiles", "SecretSpace-logo.png"),
os.path.join("sourceFiles", "SecretSpace-logo.ico"),
os.path.join("installer", "SecretSpace.iss"),
os.path.join("installer", "installed"),
)

Expand Down Expand Up @@ -83,7 +83,7 @@ def run(cmd, cwd=None, env=None, on_line=None) -> Result:
# ---------------------------------------------------------------------------
def project_dir() -> str:
"""
The PassMan folder: the one holding PassMan-Builder.exe when frozen,
The SecretSpace folder: the one holding SecretSpace-Builder.exe when frozen,
else the parent of builder/ where this file lives.
"""
if getattr(sys, "frozen", False):
Expand Down Expand Up @@ -115,7 +115,7 @@ def file_version(version: str) -> str:
m = re.fullmatch(r"(\d+)\.(\d+)\.(\d+)(?:-(?:alpha|beta|rc)\.(\d+))?",
version)
if not m:
raise ValueError(f"Not a PassMan version: {version!r}")
raise ValueError(f"Not a SecretSpace version: {version!r}")
return ".".join(g or "0" for g in m.groups())


Expand Down Expand Up @@ -183,15 +183,15 @@ def check_all(project: str, runner=run, find_iscc=find_iscc) -> list:
checks = []
gone = missing_files(project)
checks.append(Check(
"PassMan source files", not gone,
"SecretSpace source files", not gone,
"all present" if not gone else
"missing: " + ", ".join(gone) + ". Copy the whole project folder.",
None if not gone else "manual"))

py = find_python(runner)
if py is None:
checks.append(Check("Python 3", False, "not found", "winget-python"))
for name in ("tkinter", "PassMan's libraries", "PyInstaller",
for name in ("tkinter", "SecretSpace's libraries", "PyInstaller",
"The app imports cleanly"):
checks.append(Check(name, False, "needs Python first"))
else:
Expand All @@ -208,7 +208,7 @@ def check_all(project: str, runner=run, find_iscc=find_iscc) -> list:
missing = [pip for mod, pip in APP_PACKAGES
if runner(py.cmd + ["-c", f"import {mod}"]).code != 0]
checks.append(Check(
"PassMan's libraries", not missing,
"SecretSpace's libraries", not missing,
"ok" if not missing else "missing: " + ", ".join(missing),
None if not missing else "pip", missing))

Expand Down Expand Up @@ -257,10 +257,10 @@ def fix_commands(checks, project: str, py: "Python" = None) -> list:
elif c.fix == "winget-inno":
out.append(("Install Inno Setup 6", winget + [WINGET_INNO]))
elif c.fix == "pip" and py is not None:
if c.name == "PassMan's libraries":
if c.name == "SecretSpace's libraries":
cmd = py.cmd + ["-m", "pip", "install", "-r",
os.path.join(project, "requirements.txt")]
out.append(("Install PassMan's libraries", cmd))
out.append(("Install SecretSpace's libraries", cmd))
else:
out.append((f"Install {', '.join(c.packages)}",
py.cmd + ["-m", "pip", "install"] + c.packages))
Expand All @@ -280,14 +280,17 @@ def remove_previous(project: str) -> None:
successful one because last time's files are still there."""
import shutil
# PassMan.exe here is the portable exe of 2.2 and earlier, no longer
# built: removed so it cannot pass for a current build.
for name in ("PassMan.exe", "PassMan-Client-Installer.exe"):
# built, and the PassMan-* names are those from before the rename to
# SecretSpace: removed so none of them can pass for a current build.
for name in ("SecretSpace.exe", "SecretSpace-Client-Installer.exe",
"PassMan.exe", "PassMan-Client-Installer.exe"):
path = os.path.join(project, name)
if os.path.exists(path):
os.remove(path)
folder = os.path.join(project, "build", "installer", "PassMan")
if os.path.isdir(folder):
shutil.rmtree(folder)
for folder in (os.path.join(project, "build", "installer", "SecretSpace"),
os.path.join(project, "build", "installer", "PassMan")):
if os.path.isdir(folder):
shutil.rmtree(folder)


def build_steps(project: str, py: "Python", iscc: str, version: str) -> list:
Expand All @@ -300,22 +303,22 @@ def build_steps(project: str, py: "Python", iscc: str, version: str) -> list:
"--distpath", os.path.join(project, "build", "installer"),
"--workpath", os.path.join(project, "build",
"installer-work"),
os.path.join(src, "PassMan-installer.spec")],
os.path.join(project, "build", "installer", "PassMan",
"PassMan.exe")),
("Packing PassMan-Client-Installer.exe",
os.path.join(src, "SecretSpace-installer.spec")],
os.path.join(project, "build", "installer", "SecretSpace",
"SecretSpace.exe")),
("Packing SecretSpace-Client-Installer.exe",
[iscc, "/Q", f"/DAppVersion={version}",
f"/DFileVersion={file_version(version)}",
os.path.join(project, "installer", "PassMan.iss")],
os.path.join(project, "PassMan-Client-Installer.exe")),
os.path.join(project, "installer", "SecretSpace.iss")],
os.path.join(project, "SecretSpace-Client-Installer.exe")),
]


def build(project: str, py: "Python", iscc: str, log=print, runner=run,
exists=os.path.isfile) -> bool:
"""Run the build, logging as it goes. True when every output exists."""
version = app_version(project)
log(f"PassMan {version}")
log(f"SecretSpace {version}")
remove_previous(project)
for label, cmd, product in build_steps(project, py, iscc, version):
log("")
Expand All @@ -327,31 +330,31 @@ def build(project: str, py: "Python", iscc: str, log=print, runner=run,
"The messages above give the reason.")
return False
log("")
log(f"DONE. Version {version}: PassMan-Client-Installer.exe")
log(f"DONE. Version {version}: SecretSpace-Client-Installer.exe")
return True


def build_builder(project: str, py: "Python", log=print, runner=run) -> bool:
"""Freeze this module and builder_gui.py into PassMan-Builder.exe."""
product = os.path.join(project, "PassMan-Builder.exe")
"""Freeze this module and builder_gui.py into SecretSpace-Builder.exe."""
product = os.path.join(project, "SecretSpace-Builder.exe")
if os.path.exists(product):
os.remove(product)
res = runner(py.cmd + ["-m", "PyInstaller", "--noconfirm",
"--distpath", project,
"--workpath", os.path.join(project, "build"),
os.path.join(project, "builder",
"PassMan-Builder.spec")],
"SecretSpace-Builder.spec")],
cwd=project, on_line=log)
ok = res.code == 0 and os.path.isfile(product)
log("DONE: PassMan-Builder.exe" if ok else
f"FAILED to build PassMan-Builder.exe (exit code {res.code})")
log("DONE: SecretSpace-Builder.exe" if ok else
f"FAILED to build SecretSpace-Builder.exe (exit code {res.code})")
return ok


# ---------------------------------------------------------------------------
# Terminal use: py builder\build.py [--install-missing] [--builder]
# --install-missing install what is missing, then build
# --builder build PassMan-Builder.exe instead of the app
# --builder build SecretSpace-Builder.exe instead of the app
# ---------------------------------------------------------------------------
def main(argv=None) -> int:
argv = sys.argv[1:] if argv is None else argv
Expand Down
14 changes: 7 additions & 7 deletions builder/builder_gui.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""
PassMan Builder: the window around build.py.
SecretSpace Builder: the window around build.py.

It shows every prerequisite as a checklist, offers to install what is
missing (asking before each install), then builds
PassMan-Client-Installer.exe with a live log. All the work happens in build.py, on a background thread, so the
SecretSpace-Client-Installer.exe with a live log. All the work happens in build.py, on a background thread, so the
window never freezes during a long build.
"""

Expand All @@ -13,7 +13,7 @@
import threading
from tkinter import filedialog, messagebox

# Reuse PassMan's own look. When frozen, PyInstaller has bundled theme.py.
# Reuse SecretSpace's own look. When frozen, PyInstaller has bundled theme.py.
if not getattr(sys, "frozen", False):
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(
os.path.abspath(__file__))), "sourceFiles"))
Expand All @@ -37,10 +37,10 @@ def __init__(self, root, project):
self.checks = []
self.python = None

root.title("PassMan Builder")
root.title("SecretSpace Builder")
root.geometry("760x640")
root.minsize(640, 520)
for path in asset_candidates("PassMan-logo.ico"):
for path in asset_candidates("SecretSpace-logo.ico"):
if os.path.exists(path):
try:
root.iconbitmap(path)
Expand All @@ -53,7 +53,7 @@ def __init__(self, root, project):

head = ctk.CTkFrame(root, fg_color=HEADER_BG, corner_radius=0)
head.grid(row=0, column=0, sticky="ew")
ctk.CTkLabel(head, text="PassMan Builder", font=F(20, "bold"),
ctk.CTkLabel(head, text="SecretSpace Builder", font=F(20, "bold"),
text_color=GREEN).pack(anchor="w", padx=20, pady=(14, 0))
self.where = ctk.CTkLabel(head, text="", font=F(12),
text_color=MUTED, anchor="w")
Expand Down Expand Up @@ -236,7 +236,7 @@ def done(ok):

def choose_folder(self):
folder = filedialog.askdirectory(
parent=self.root, title="Choose the PassMan project folder",
parent=self.root, title="Choose the SecretSpace project folder",
initialdir=self.project)
if folder:
self.project = os.path.normpath(folder)
Expand Down
Loading
Loading