Add drop-folder ingestion and scoped uploader IAM user
Complete Phase 1 ingestion. Add a least-privilege IAM user
(apm-wo-drop-uploader) to the pipeline stack, scoped to s3:PutObject
on the raw/ prefix only — the local launchd uploader authenticates as
this user via a dedicated profile, so a laptop credential leak cannot
read, list, or touch the analytics data.
Replace the scaffold uploader stub with the hardened stampli-pattern
script (lockfile, logging, timestamped archive, notifications, settle
delay) and align names to the convention (~/apm-wo-drop, ~/.local/bin,
com.seahaven.apm-wo-uploader). The plist sets PATH/HOME because launchd
runs with a stripped environment and otherwise cannot find aws.
The exports bucket already shipped in the Phase 0 scaffold, so the code
delta here is the uploader identity and tooling.
2026-05-28 16:32:56 -04:00
|
|
|
#!/bin/bash
|
|
|
|
|
# apm-wo-analysis local drop-folder uploader (optional zero-touch ingestion).
|
|
|
|
|
#
|
|
|
|
|
# Mirrors the proven stampli-drop-folder pattern. launchd invokes the INSTALLED
|
|
|
|
|
# copy at ~/.local/bin/apm-wo-uploader.sh, which must live OUTSIDE ~/Documents:
|
|
|
|
|
# macOS TCC denies launchd read access to ~/Documents, ~/Desktop, ~/Downloads,
|
|
|
|
|
# and a repo-path script fails silently with LastExitStatus=32256. Re-copy this
|
|
|
|
|
# source to ~/.local/bin after editing it.
|
|
|
|
|
#
|
|
|
|
|
# Uploads new .xlsx/.csv exports to the raw/ prefix using the scoped `apm-wo-drop`
|
|
|
|
|
# profile (IAM user apm-wo-drop-uploader — s3:PutObject on raw/ only), then
|
|
|
|
|
# archives them locally. The classifier Lambda is S3-triggered from raw/.
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
DROP_DIR="$HOME/apm-wo-drop"
|
|
|
|
|
UPLOADED_DIR="$DROP_DIR/uploaded"
|
2026-08-13 17:39:11 -04:00
|
|
|
# Log and lock MUST sit outside WatchPaths (~/apm-wo-drop). Writing them
|
|
|
|
|
# inside the drop folder retriggers launchd on every log line and lock
|
|
|
|
|
# mkdir/rmdir, which is what ballooned .upload.log to tens of MB.
|
|
|
|
|
STATE_DIR="$HOME/.local/log"
|
|
|
|
|
LOG_FILE="$STATE_DIR/apm-wo-uploader.log"
|
|
|
|
|
LOCK_DIR="$STATE_DIR/apm-wo-uploader.lock"
|
2026-09-16 20:33:24 +00:00
|
|
|
BUCKET="apm-wo-analysis-exports-011934824531"
|
Add drop-folder ingestion and scoped uploader IAM user
Complete Phase 1 ingestion. Add a least-privilege IAM user
(apm-wo-drop-uploader) to the pipeline stack, scoped to s3:PutObject
on the raw/ prefix only — the local launchd uploader authenticates as
this user via a dedicated profile, so a laptop credential leak cannot
read, list, or touch the analytics data.
Replace the scaffold uploader stub with the hardened stampli-pattern
script (lockfile, logging, timestamped archive, notifications, settle
delay) and align names to the convention (~/apm-wo-drop, ~/.local/bin,
com.seahaven.apm-wo-uploader). The plist sets PATH/HOME because launchd
runs with a stripped environment and otherwise cannot find aws.
The exports bucket already shipped in the Phase 0 scaffold, so the code
delta here is the uploader identity and tooling.
2026-05-28 16:32:56 -04:00
|
|
|
PROFILE="${APM_WO_AWS_PROFILE:-apm-wo-drop}"
|
|
|
|
|
|
2026-08-13 17:39:11 -04:00
|
|
|
mkdir -p "$UPLOADED_DIR" "$STATE_DIR"
|
Add drop-folder ingestion and scoped uploader IAM user
Complete Phase 1 ingestion. Add a least-privilege IAM user
(apm-wo-drop-uploader) to the pipeline stack, scoped to s3:PutObject
on the raw/ prefix only — the local launchd uploader authenticates as
this user via a dedicated profile, so a laptop credential leak cannot
read, list, or touch the analytics data.
Replace the scaffold uploader stub with the hardened stampli-pattern
script (lockfile, logging, timestamped archive, notifications, settle
delay) and align names to the convention (~/apm-wo-drop, ~/.local/bin,
com.seahaven.apm-wo-uploader). The plist sets PATH/HOME because launchd
runs with a stripped environment and otherwise cannot find aws.
The exports bucket already shipped in the Phase 0 scaffold, so the code
delta here is the uploader identity and tooling.
2026-05-28 16:32:56 -04:00
|
|
|
exec >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
|
|
|
|
# Single-flight: WatchPaths can fire several times for one save.
|
|
|
|
|
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
|
|
|
echo "$(date '+%Y-%m-%dT%H:%M:%S') skipping — another run holds the lock"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
trap 'rmdir "$LOCK_DIR" 2>/dev/null || true' EXIT
|
|
|
|
|
|
|
|
|
|
# Let the file finish writing before uploading.
|
|
|
|
|
sleep 2
|
|
|
|
|
|
|
|
|
|
shopt -s nullglob
|
|
|
|
|
uploaded_count=0
|
|
|
|
|
failed_count=0
|
|
|
|
|
|
|
|
|
|
for f in "$DROP_DIR"/*.xlsx "$DROP_DIR"/*.csv; do
|
|
|
|
|
[ -f "$f" ] || continue
|
|
|
|
|
name=$(basename "$f")
|
|
|
|
|
ts=$(date '+%Y%m%d-%H%M%S')
|
|
|
|
|
|
|
|
|
|
echo "$(date '+%Y-%m-%dT%H:%M:%S') uploading $name"
|
|
|
|
|
if aws --profile "$PROFILE" s3 cp "$f" "s3://$BUCKET/raw/$name"; then
|
|
|
|
|
mv "$f" "$UPLOADED_DIR/$ts-$name"
|
|
|
|
|
echo "$(date '+%Y-%m-%dT%H:%M:%S') OK -> uploaded/$ts-$name"
|
|
|
|
|
uploaded_count=$((uploaded_count + 1))
|
|
|
|
|
osascript -e "display notification \"Uploaded $name\" with title \"APM WO Uploader\"" 2>/dev/null || true
|
|
|
|
|
else
|
|
|
|
|
echo "$(date '+%Y-%m-%dT%H:%M:%S') FAIL $name"
|
|
|
|
|
failed_count=$((failed_count + 1))
|
2026-08-13 17:39:11 -04:00
|
|
|
osascript -e "display notification \"Failed to upload $name — see $LOG_FILE\" with title \"APM WO Uploader\" sound name \"Basso\"" 2>/dev/null || true
|
Add drop-folder ingestion and scoped uploader IAM user
Complete Phase 1 ingestion. Add a least-privilege IAM user
(apm-wo-drop-uploader) to the pipeline stack, scoped to s3:PutObject
on the raw/ prefix only — the local launchd uploader authenticates as
this user via a dedicated profile, so a laptop credential leak cannot
read, list, or touch the analytics data.
Replace the scaffold uploader stub with the hardened stampli-pattern
script (lockfile, logging, timestamped archive, notifications, settle
delay) and align names to the convention (~/apm-wo-drop, ~/.local/bin,
com.seahaven.apm-wo-uploader). The plist sets PATH/HOME because launchd
runs with a stripped environment and otherwise cannot find aws.
The exports bucket already shipped in the Phase 0 scaffold, so the code
delta here is the uploader identity and tooling.
2026-05-28 16:32:56 -04:00
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [ $uploaded_count -eq 0 ] && [ $failed_count -eq 0 ]; then
|
|
|
|
|
echo "$(date '+%Y-%m-%dT%H:%M:%S') folder change triggered but no .xlsx/.csv files found"
|
|
|
|
|
fi
|