#!/bin/bash
#
# package_source.sh — build the GPL "complete corresponding source" zip.
#
# SwanDesk links strongSwan's libvici into the privileged helper, which makes the
# helper a combined work under the GNU GPL v2 (strongSwan's license). When we
# distribute the binary we must therefore make the corresponding source
# available. This packages SwanDesk's own source + build system (allowlisted —
# see SRC_ITEMS).
#
# It does NOT redistribute the strongSwan / OpenSSL source tarballs (they are
# large, unmodified upstream releases). GPL still requires the distributor — not
# a third-party website — to provide that source, so instead of bundling it the
# package carries a WRITTEN OFFER (GPLv2 §3b) in README-SOURCE.txt, with the
# exact version + SHA-256 of each release so the offer is precise. Honoring it is
# just handing over the unchanged tarball. (Our build applies no patches, so the
# pristine tarball + Scripts/ is the complete corresponding source.)
#
# It deliberately uses an ALLOWLIST (only known-source paths are copied — never
# the whole tree) and then runs a SECRET SCAN that ABORTS the package if anything
# sensitive slipped in: signing/cert/key material, the deploy host, .env files,
# VCS history, or build artifacts. So company credentials cannot ship by mistake.
#
# Usage: Scripts/package_source.sh <version> <output-zip>
#   <version>     version string used for the top-level dir name in the zip
#   <output-zip>  path to write (its directory must already exist)

set -euo pipefail

VERSION="${1:?usage: package_source.sh <version> <output-zip>}"
OUT_ZIP_ARG="${2:?usage: package_source.sh <version> <output-zip>}"

# Repo root = parent of this script's directory.
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"

# Resolve the output path to absolute now (we cd into the staging dir to zip).
OUT_DIR="$(cd "$(dirname "$OUT_ZIP_ARG")" && pwd)"
OUT_ZIP="$OUT_DIR/$(basename "$OUT_ZIP_ARG")"

PKG="SwanDesk-${VERSION}-src"
STAGE="$OUT_DIR/.srcpkg-staging"
DEST="$STAGE/$PKG"

rm -rf "$STAGE" "$OUT_ZIP"
mkdir -p "$DEST"

# --- 1. Allowlist: SwanDesk's own source + build system ---------------------
# Add new top-level source files/dirs here. Anything NOT listed is never copied.
SRC_ITEMS=(
  project.yml
  Makefile
  README.md
  .gitignore
  CVici
  Scripts
  Shared
  SwanDesk
  SwanDeskHelper
)
for item in "${SRC_ITEMS[@]}"; do
  if [ -e "$item" ]; then
    ditto "$item" "$DEST/$item"   # ditto creates intermediate dirs; handles files + trees
  else
    echo "warning: source item '$item' not found, skipping" >&2
  fi
done

# --- 2. Record provenance of the bundled upstream source --------------------
# We do NOT redistribute the strongSwan/OpenSSL tarballs here (unmodified upstream
# releases). Instead capture each one's exact version + SHA-256 so the written
# offer below is precise and verifiable. The tarballs must be present in src/ —
# the binary being shipped was built from them.
#
# Contact address for the written offer. Override with SOURCE_OFFER_CONTACT=...
SOURCE_OFFER_CONTACT="${SOURCE_OFFER_CONTACT:-source@juvex.fi}"
UPSTREAM_BLOCK=""
for tb in src/strongswan-*.tar.gz src/openssl-*.tar.gz src/dnsmasq-*.tar.gz; do
  if [ ! -f "$tb" ]; then
    echo "error: $tb not found in src/ — needed to record the source's exact" >&2
    echo "       version + checksum for the GPL written offer." >&2
    rm -rf "$STAGE"
    exit 1
  fi
  base="$(basename "$tb")"
  sha="$(shasum -a 256 "$tb" | awk '{print $1}')"
  UPSTREAM_BLOCK="${UPSTREAM_BLOCK}  ${base}
    SHA-256: ${sha}
"
done

# --- 3. Sanitize the Makefile: strip private deploy/notary infra defaults ----
# The recipes still work; downstream builders set their own values on the CLI.
# (The signing identity / team id are intentionally left in — they are embedded
# in every distributed signed binary and are not secret.)
MK="$DEST/Makefile"
/usr/bin/sed -i '' \
  -e 's|^NOTARY_PROFILE ?=.*|NOTARY_PROFILE ?= YOUR_NOTARY_PROFILE|' \
  -e 's|^DEPLOY_HOST  ?=.*|DEPLOY_HOST  ?= user@example.com|' \
  -e 's|^DEPLOY_PATH  ?=.*|DEPLOY_PATH  ?= /path/to/SwanDesk.dmg|' \
  -e 's|^DEPLOY_SRC_PATH ?=.*|DEPLOY_SRC_PATH ?= $(dir $(DEPLOY_PATH))$(SCHEME)-src.zip|' \
  "$MK"

# --- 4. Source-offer / build note -------------------------------------------
cat > "$DEST/README-SOURCE.txt" <<EOF
SwanDesk — corresponding source (GNU GPL v2)
============================================

Version: ${VERSION}

SwanDesk bundles and links strongSwan (https://www.strongswan.org/) and dnsmasq
(https://thekelleys.org.uk/dnsmasq/doc.html), both licensed under the GNU
General Public License, version 2 (or later). This archive contains the source
of SwanDesk itself and its build system. The source for the bundled strongSwan,
OpenSSL, and dnsmasq is offered separately (see below).

Contents
--------
  project.yml, Makefile, Scripts/   Build system (XcodeGen + xcodebuild + the
                                    OpenSSL/strongSwan build scripts).
  SwanDesk/, SwanDeskHelper/,        SwanDesk application + privileged helper +
  Shared/, CVici/                    shared code.

Bundled third-party source (offered, not included here)
--------------------------------------------------------
SwanDesk ships unmodified upstream releases of strongSwan, OpenSSL, and dnsmasq,
built by the scripts in Scripts/ with no source patches. The exact releases
bundled in this version are:

${UPSTREAM_BLOCK}
WRITTEN OFFER: For at least three (3) years from the date you received this
software, Juvex Oy will provide, to any third party, a complete machine-readable
copy of the corresponding source code for the strongSwan, OpenSSL, and dnsmasq
releases identified above, for no more than the cost of physically performing
the distribution. Request it from: ${SOURCE_OFFER_CONTACT}

The identical releases are also published upstream as a convenience — verify the
SHA-256 above:
  strongSwan: https://download.strongswan.org/
  OpenSSL:    https://www.openssl.org/source/
  dnsmasq:    https://www.thekelleys.org.uk/dnsmasq/

Licenses
--------
  strongSwan: GNU GPL v2 (with an OpenSSL/LGPL linking exception) — see
              LICENSE / COPYING inside the strongSwan release.
  dnsmasq:    GNU GPL v2 — see COPYING inside the dnsmasq release.
  OpenSSL 3:  Apache License 2.0 — see LICENSE.txt inside the OpenSSL release.

Building
--------
  Requirements: Xcode, \`brew install xcodegen\`, an Apple Developer ID signing
  identity, and the strongSwan + OpenSSL tarballs above placed in src/. The
  dnsmasq tarball is downloaded automatically on first build. Then:

      make build          # debug
      make build-release  # release

  \`make build\` builds OpenSSL, strongSwan, and dnsmasq from src/ on first run.
  See README.md for the architecture and build details.

Note: signing identities, notarization credentials, and deployment hosts are
NOT included in this archive — substitute your own in the Makefile / project.yml.
EOF

# --- 5. Prune editor/OS/VCS cruft that ditto may have carried over ----------
find "$DEST" -name '.DS_Store' -delete
find "$DEST" -type d -name 'xcuserdata' -prune -exec rm -rf {} +
find "$DEST" -type d -name '.git'       -prune -exec rm -rf {} +
find "$DEST" -name '*.xcuserstate' -delete

# --- 6. Secret scan (defense in depth): ABORT if anything sensitive remains --
fail=0

# 6a. Forbidden file types — signing/cert/key material, provisioning, env files.
#     (The strongSwan/OpenSSL tarballs are .tar.gz, so test certs inside them are
#      invisible to find — only loose files in our own tree are checked.)
while IFS= read -r f; do
  echo "SECRET-SCAN: forbidden file present: ${f#$DEST/}" >&2
  fail=1
done < <(find "$DEST" \( \
       -name '*.key' -o -name '*.pem' -o -name '*.pfx' -o -name '*.p12' \
    -o -name '*.cer' -o -name '*.crt' -o -name '*.der' \
    -o -name '*.mobileprovision' -o -name '*.provisionprofile' \
    -o -name '.env' -o -name '.env.*' -o -name 'id_rsa*' \) -print)

# 6b. Generic high-signal secrets — private keys + cloud credentials. grep -I
#     skips binary files, so the source tarballs are not scanned line-by-line.
#     This scanner's own file is excluded: it necessarily contains these patterns
#     as literals, and (by 6c's design) holds no real secret of its own.
SECRET_RE='-----BEGIN [A-Z ]*PRIVATE KEY-----|AKIA[0-9A-Z]{16}|aws_secret_access_key'
if grep -rIlE --exclude='package_source.sh' "$SECRET_RE" "$DEST" >/dev/null 2>&1; then
  echo "SECRET-SCAN: private-key / cloud-credential material found in:" >&2
  grep -rIlE --exclude='package_source.sh' "$SECRET_RE" "$DEST" | sed "s#^$DEST/#  #" >&2
  fail=1
fi

# 6c. Deploy infra backstop. Instead of hardcoding the host/path here (which would
#     bake them into — and ship them with — this very script), derive the real
#     values from the source Makefile and assert that NEITHER survives anywhere in
#     the package. Catches a broken sanitizer in section 3, and auto-tracks any
#     future change to DEPLOY_HOST / DEPLOY_PATH.
while IFS= read -r tok; do
  [ -n "$tok" ] || continue
  if grep -rIlF "$tok" "$DEST" >/dev/null 2>&1; then
    echo "SECRET-SCAN: deploy infra value leaked into:" >&2
    grep -rIlF "$tok" "$DEST" | sed "s#^$DEST/#  #" >&2
    fail=1
  fi
done < <(grep -hE '^(DEPLOY_HOST|DEPLOY_PATH)[[:space:]]' Makefile \
           | sed -E 's/^[A-Z_]+[[:space:]]*\?=[[:space:]]*//')

if [ "$fail" -ne 0 ]; then
  echo "ERROR: secret scan failed — source package NOT created." >&2
  echo "       Remove the offending material (or extend the allowlist/sanitizer) and retry." >&2
  rm -rf "$STAGE"
  exit 1
fi

# --- 7. Zip (top-level dir = SwanDesk-<version>-src) -------------------------
( cd "$STAGE" && /usr/bin/zip -r -q -X "$OUT_ZIP" "$PKG" )
rm -rf "$STAGE"

echo "Created source package: $OUT_ZIP"
echo "  $(du -h "$OUT_ZIP" | cut -f1) — top-level dir: $PKG/"
