Skip to content
Draft
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
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ MACFTPD_CODESIGN_KEYCHAIN_PASS_FILE='/opt/macftpd/var/macftpd-codesign.keychain.
./scripts/deploy-remote-macos.sh
```

Run the installer on the remote Mac itself. It prints the three values needed by the deploy script. If no stable identity is configured, deploy falls back to ad-hoc signing and warns that macOS may ask for removable-volume access again after a binary replacement.
Run the installer on the remote Mac itself. It prints the three values needed by the deploy script. The installer registers its custom keychain in the user search list and grants `codesign` access to the private key. If macOS asks for an interactive trust approval, use the exact case-sensitive `trustRoot` and `codeSign` values printed by the installer. When any stable-signing option is configured, deployment signs and verifies the staged binary before replacing the active one and fails closed if the identity cannot be used. Ad-hoc signing is retained only for deploys with no stable identity configured and may cause macOS to ask for removable-volume access again after a binary replacement.

Override `REMOTE_DIR` and `STORAGE_ROOT` for site-specific installs, for example a home-directory app folder with an external-volume FTP root:

Expand All @@ -96,6 +96,8 @@ ADMIN_PASS='same-password' ./scripts/smoke-remote.sh
ADMIN_PASS='same-password' HOST=192.0.2.10 ./scripts/protocol-lab.sh
```

Prefer a numeric LAN IPv4 address for the protocol lab when `.local` resolution exposes both link-local IPv6 and IPv4 routes. The lab intentionally exercises a long-lived FTP control connection, so a lossy link-local route can fail even while loopback monitoring and the IPv4 service are healthy.

## Cloudflare HTTP Front Door

`https://ftp.example.com` is served through Cloudflare Tunnel and a Worker:
Expand All @@ -113,6 +115,13 @@ TUNNEL_TOKEN_FILE=/path/to/token ./scripts/start-cloudflare-tunnel.sh

The token is stored on the remote Mac at `/opt/macftpd/var/cloudflared.env.token` with mode `0600`, and the screen session is `macftpd-cloudflared`.

Upgrade the bundled connector with the release-pinned, checksum-verified helper. A remote upgrade keeps a timestamped previous binary and restarts only the connector LaunchAgent:

```bash
REMOTE='macftpd@example-host.local' KEY='/path/to/ssh-key' \
REMOTE_DIR='/opt/macftpd' ./scripts/upgrade-cloudflared.sh
```

Deploy or repair the Worker route:

```bash
Expand Down
52 changes: 43 additions & 9 deletions scripts/deploy-remote-macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,38 +98,72 @@ set -euo pipefail
REMOTE_DIR="${REMOTE_DIR:-/opt/macftpd}"
sign_macftpd() {
local binary="$1"
if ! command -v codesign >/dev/null 2>&1; then
return 0
fi
local identity="${MACFTPD_CODESIGN_IDENTITY:-}"
local keychain="${MACFTPD_CODESIGN_KEYCHAIN:-}"
local pass_file="${MACFTPD_CODESIGN_KEYCHAIN_PASS_FILE:-}"
local stable_requested=false
if [[ -n "${identity}" || -n "${keychain}" || -n "${pass_file}" ]]; then
stable_requested=true
fi
if ! command -v codesign >/dev/null 2>&1; then
if [[ "${stable_requested}" == true ]]; then
echo "error: stable codesigning was requested, but codesign is unavailable" >&2
return 1
fi
return 0
fi
local keychain_args=()
if [[ -n "${keychain}" ]]; then
keychain_args=(--keychain "${keychain}")
if [[ -n "${pass_file}" && -f "${pass_file}" ]]; then
security unlock-keychain -p "$(cat "${pass_file}")" "${keychain}" >/dev/null 2>&1 || true
local keychain_pass
keychain_pass="$(cat "${pass_file}")"
security unlock-keychain -p "${keychain_pass}" "${keychain}"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${keychain_pass}" "${keychain}" >/dev/null
fi

local search_output entry found=false
local -a search_list=()
search_output="$(security list-keychains -d user)"
while IFS= read -r entry; do
entry="${entry#"${entry%%[![:space:]]*}"}"
entry="${entry#\"}"
entry="${entry%\"}"
[[ -n "${entry}" ]] || continue
search_list+=("${entry}")
if [[ "${entry}" == "${keychain}" ]]; then
found=true
fi
done <<<"${search_output}"
if [[ "${found}" != true ]]; then
security list-keychains -d user -s "${search_list[@]}" "${keychain}"
fi
fi
if [[ -z "${identity}" && -n "${keychain}" ]]; then
identity="$(security find-identity -v -p codesigning "${keychain}" 2>/dev/null | awk -F '"' '/"/ {print $2; exit}')"
fi
if [[ -n "${identity}" ]]; then
if codesign --force --sign "${identity}" "${keychain_args[@]}" --identifier org.rememe.macftpd "${binary}"; then
return 0
if codesign --verify --strict --verbose=2 "${binary}"; then
return 0
fi
fi
echo "warning: stable codesign identity failed; falling back to ad-hoc signing" >&2
else
echo "warning: no stable codesign identity configured; ad-hoc signing may retrigger macOS removable-volume prompts after each upgrade" >&2
echo "error: stable codesign identity failed; refusing to replace the deployed binary" >&2
return 1
fi
if [[ "${stable_requested}" == true ]]; then
echo "error: stable codesigning was requested, but no valid identity was found" >&2
return 1
fi
echo "warning: no stable codesign identity configured; ad-hoc signing may retrigger macOS removable-volume prompts after each upgrade" >&2
codesign --force --sign - --identifier org.rememe.macftpd "${binary}"
}
chmod 755 "${REMOTE_DIR}/bin/macftpd.new"
sign_macftpd "${REMOTE_DIR}/bin/macftpd.new"
if [[ -f "${REMOTE_DIR}/bin/macftpd" ]]; then
cp "${REMOTE_DIR}/bin/macftpd" "${REMOTE_DIR}/bin/macftpd.prev.$(date -u +%Y%m%dT%H%M%SZ)"
fi
mv "${REMOTE_DIR}/bin/macftpd.new" "${REMOTE_DIR}/bin/macftpd"
sign_macftpd "${REMOTE_DIR}/bin/macftpd"
if [[ ! -f "${REMOTE_DIR}/config.json" ]]; then
mv "${REMOTE_DIR}/config.json.new" "${REMOTE_DIR}/config.json"
else
Expand Down
19 changes: 18 additions & 1 deletion scripts/install-macos-codesign-identity.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ if [[ ! -f "${KEYCHAIN}" ]]; then
fi
security unlock-keychain -p "${PASS}" "${KEYCHAIN}"

search_output="$(security list-keychains -d user)"
search_list=()
found=false
while IFS= read -r entry; do
entry="${entry#"${entry%%[![:space:]]*}"}"
entry="${entry#\"}"
entry="${entry%\"}"
[[ -n "${entry}" ]] || continue
search_list+=("${entry}")
if [[ "${entry}" == "${KEYCHAIN}" ]]; then
found=true
fi
done <<<"${search_output}"
if [[ "${found}" != true ]]; then
security list-keychains -d user -s "${search_list[@]}" "${KEYCHAIN}"
fi

cat >"${OPENSSL_CONFIG}" <<EOF
[ req ]
distinguished_name = dn
Expand Down Expand Up @@ -51,8 +68,8 @@ if ! security find-identity -v -p codesigning "${KEYCHAIN}" | grep -F "\"${SIGN_
-password "pass:${PASS}" \
-name "${SIGN_NAME}" >/dev/null 2>&1
security import "${P12}" -k "${KEYCHAIN}" -P "${PASS}" -T /usr/bin/codesign >/dev/null
security set-key-partition-list -S apple-tool:,apple: -s -k "${PASS}" "${KEYCHAIN}" >/dev/null 2>&1 || true
fi
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${PASS}" "${KEYCHAIN}" >/dev/null

if ! security find-identity -v -p codesigning "${KEYCHAIN}" | grep -F "\"${SIGN_NAME}\"" >/dev/null 2>&1; then
cat >&2 <<EOF
Expand Down
4 changes: 2 additions & 2 deletions scripts/upgrade-cloudflared.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail

VERSION="${VERSION:-2026.6.1}"
VERSION="${VERSION:-2026.7.2}"
REMOTE="${REMOTE:-}"
KEY="${KEY:-}"
REMOTE_DIR="${REMOTE_DIR:-/opt/macftpd}"
INSTALL_PATH="${INSTALL_PATH:-${REMOTE_DIR}/bin/cloudflared}"
EXPECTED_BINARY_SHA256_DARWIN_ARM64="${EXPECTED_BINARY_SHA256_DARWIN_ARM64:-ae6ee90188ae5833c687ce937c3693e28403677607c06c65a2ff2b6a022f50e4}"
EXPECTED_BINARY_SHA256_DARWIN_ARM64="${EXPECTED_BINARY_SHA256_DARWIN_ARM64:-0588df58494a6cadd38b9deb6078908a5054063c80784d92fdb8d4a5f3de1c67}"
ASSET="cloudflared-darwin-arm64.tgz"
URL="https://github.com/cloudflare/cloudflared/releases/download/${VERSION}/${ASSET}"
SSH_OPTS=()
Expand Down
6 changes: 3 additions & 3 deletions scripts/weekly-report.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ PY
printf -- '- local_http: failed `%s`\n' "$(cat /tmp/macftpd-weekly-health-err.$$ 2>/dev/null || true)"
fi
rm -f /tmp/macftpd-weekly-health.$$ /tmp/macftpd-weekly-health-err.$$
if command -v cloudflared >/dev/null 2>&1; then
printf -- '- system_cloudflared: `%s`\n' "$(cloudflared --version 2>/dev/null || true)"
elif [[ -x "${APP_DIR}/bin/cloudflared" ]]; then
if [[ -x "${APP_DIR}/bin/cloudflared" ]]; then
printf -- '- bundled_cloudflared: `%s`\n' "$("${APP_DIR}/bin/cloudflared" --version 2>/dev/null || true)"
elif command -v cloudflared >/dev/null 2>&1; then
printf -- '- system_cloudflared: `%s`\n' "$(cloudflared --version 2>/dev/null || true)"
fi
if [[ -x "${APP_DIR}/bin/macftpd" ]]; then
printf -- '- bundled_macftpd: present\n'
Expand Down