Compare commits

..

5 Commits

Author SHA1 Message Date
0a298b2f6e Add the Entra app-registration request for the SharePoint file picker
The written request for a `WP-Suite-File-Picker` app registration in the
prime-controls.com tenant: SPA platform, delegated Files.Read.All / Sites.Read.All
/ User.Read, admin consent, no secret and no app-only permissions. Keeping it in
the repo means the lowest-privilege configuration we asked IT for is on the record
next to the code that will use it, and whoever wires up the picker can see what
was actually granted rather than reconstructing it from memory.

Dated 2026-06-15, authored by Nick Siegfried. Committed on its own branch off main
because it has nothing to do with the user-directory work that was in flight.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 15:41:03 -05:00
3cccdf1c4b Merge branch 'docs/deploy-runbook': project archiving, default members, admin console rebuild
Brings in the 2026-08-05 work plus the deploy runbook and KNOWN-ISSUES.md.
Carries migration a7c31f9e5b02 (additive, with server defaults). No overlap with
the entrypoint/backup-script changes already on main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:46:27 -07:00
7bb1f66588 Merge pull request 'Add entrypoint fallback for missing bind-mounted scripts' (#2) from scripts/entrypoint into main
Reviewed-on: #2
2026-08-05 01:56:49 +00:00
26f4a9242a Entrypoint static variable removed to allow validation of scripting files before startup 2026-08-04 18:51:08 -07:00
6c1dc7d45f Add entrypoint fallback for missing bind-mounted scripts
- scripts/entrypoint.sh (new): prefers the live bind-mounted backup-cron.sh, but falls back to a copy baked into the image at build time if the mount is missing. If neither exists, it stays up and idle (instead of crash-looping) so the container remains reachable via console/exec for diagnosis.
- scripts/backup-cron.sh (updated): resolves db-backup.sh the same live-or-fallback way, re-checked on every loop iteration, so if the bind mount comes back healthy later, this container picks up the live scripts on its next backup run with no restart needed.
- scripts/backup.Dockerfile (updated): bakes all three scripts into the image under /app/scripts-default/ as the fallback, and sets the new wrapper as ENTRYPOINT.
2026-08-04 18:44:34 -07:00
5 changed files with 142 additions and 7 deletions

View File

@@ -0,0 +1,60 @@
# Request: Entra (Azure AD) App Registration — WP Suite SharePoint File Picker
**Requested by:** Nick Siegfried (n.siegfried@prime-controls.com)
**Date:** 2026-06-15
**Purpose:** Let users of the Work Package (WP) Suite pick files directly from the
project's SharePoint folders (Drawings / Specs / Data Sheets) and attach them to a
work package, instead of manually copying and pasting SharePoint links.
To do this the tool needs to call Microsoft's **OneDrive/SharePoint File Picker
(File Picker v8)** and **Microsoft Graph** on behalf of the signed-in Prime user.
That requires an Entra app registration. None of this grants the app standalone
access — every action runs **as the signed-in user**, so it can only see what that
user already has permission to in SharePoint.
---
## What we need created
A single **App registration** in the `prime-controls.com` (primecontrolsdallas) tenant.
| Setting | Value |
|---|---|
| **Name** | `WP-Suite-File-Picker` |
| **Supported account types** | Single tenant — *Accounts in this organizational directory only* |
| **Platform** | **Single-page application (SPA)** |
| **Redirect URI(s)** | The URL(s) the tool is hosted at — e.g. `https://<wp-suite-host>/wp-creation-index.html` (and `http://localhost:<port>/...` for testing). *Nick to confirm final host.* |
### API permissions (Microsoft Graph — **Delegated**, not Application)
| Permission | Why |
|---|---|
| `Files.Read.All` | Read files the user selects in the picker |
| `Sites.Read.All` | Resolve the SharePoint site/folder the picker browses |
| `User.Read` | Basic sign-in (usually present by default) |
> Delegated = acts as the signed-in user. We are **not** requesting application
> (app-only) permissions, so the app cannot read anything on its own.
### Admin consent
- Please **grant admin consent** for the delegated permissions above so pilot users
aren't each prompted to consent individually.
---
## What we DON'T need
- No client secret / certificate (SPA uses PKCE, no secret).
- No application (app-only) permissions.
- No write/modify permissions — read-only is sufficient for attaching files.
## What to send back to Nick
1. **Application (client) ID**
2. **Directory (tenant) ID**
3. Confirmation that the **redirect URI** was registered and **admin consent** granted.
---
### Background / scope
This is for an internal pilot of the WP Suite (Project SDE). Today users paste
SharePoint "Copy Link" URLs by hand. The app registration enables a proper
"Add files" picker scoped to the SOP's folders. Read-only, single-tenant,
delegated — lowest-privilege configuration that makes the picker work.

View File

@@ -82,7 +82,6 @@ services:
volumes: volumes:
- ./scripts:/scripts:ro - ./scripts:/scripts:ro
- ./backups:/backups - ./backups:/backups
entrypoint: ["/bin/sh", "/scripts/backup-cron.sh"]
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
db: db:

View File

@@ -1,9 +1,27 @@
#!/bin/sh #!/bin/sh
# Entry point for the `backup` sidecar container. Runs db-backup.sh on a fixed # Entry point for the `backup` sidecar container's periodic loop. Runs
# interval (default: daily). Kept deliberately simple — a sleep loop instead of a # db-backup.sh on a fixed interval (default: daily) -- a sleep loop instead of
# cron daemon — so it works in a bare postgres:16-alpine image. # a cron daemon, kept deliberately simple so it works in a bare
# postgres:16-alpine image.
#
# Resolves db-backup.sh the same way entrypoint.sh resolves this file: prefer
# the live bind-mounted copy at /scripts (so edits don't need a rebuild), fall
# back to the copy baked into the image at build time if the mount is
# missing, empty, or stale. Resolving fresh on every loop iteration also means
# that if the mount comes back healthy later (e.g. someone fixes the host
# directory) this container picks it up on the very next run, with no
# restart needed.
set -eu set -eu
resolve() {
# $1 = script filename, e.g. db-backup.sh
if [ -f "/scripts/$1" ]; then
echo "/scripts/$1"
else
echo "/app/scripts-default/$1"
fi
}
INTERVAL="${BACKUP_INTERVAL_SECONDS:-86400}" # 86400 = once a day INTERVAL="${BACKUP_INTERVAL_SECONDS:-86400}" # 86400 = once a day
echo "[backup] sidecar started; interval=${INTERVAL}s, keep=${BACKUP_KEEP:-14}, dir=${BACKUP_DIR:-/backups}" echo "[backup] sidecar started; interval=${INTERVAL}s, keep=${BACKUP_KEEP:-14}, dir=${BACKUP_DIR:-/backups}"
@@ -11,6 +29,7 @@ echo "[backup] sidecar started; interval=${INTERVAL}s, keep=${BACKUP_KEEP:-14},
# immediate restore point instead of waiting a whole interval. # immediate restore point instead of waiting a whole interval.
sleep 20 sleep 20
while true; do while true; do
sh /scripts/db-backup.sh || echo "[backup] run failed; will retry next interval" >&2 DB_BACKUP="$(resolve db-backup.sh)"
sh "$DB_BACKUP" || echo "[backup] run failed; will retry next interval" >&2
sleep "$INTERVAL" sleep "$INTERVAL"
done done

View File

@@ -1,5 +1,19 @@
# Backup sidecar image: Postgres client tools (pg_dump/psql) + openssl for # Backup sidecar image: Postgres client tools (pg_dump/psql) + openssl for
# at-rest encryption of dumps. The scripts themselves are bind-mounted at runtime # at-rest encryption of dumps.
# (see the `backup` service in docker-compose.yml), so they're not COPYed here. #
# The scripts are bind-mounted live at runtime (see the `backup` service in
# docker-compose.yml) so they can be edited without a rebuild -- but they're
# ALSO baked in here as a fallback default under /app/scripts-default/.
# entrypoint.sh prefers the live mount and only falls back to this baked-in
# copy if the mount is missing, empty, or stale. That fallback is what keeps
# a broken bind mount from crash-looping the container into an unreachable
# state (see entrypoint.sh for the full story).
FROM postgres:16-alpine FROM postgres:16-alpine
RUN apk add --no-cache openssl RUN apk add --no-cache openssl
COPY scripts/backup-cron.sh scripts/db-backup.sh scripts/db-restore.sh /app/scripts-default/
COPY scripts/entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh /app/scripts-default/*.sh
ENTRYPOINT ["/app/entrypoint.sh"]
CMD []

43
scripts/entrypoint.sh Normal file
View File

@@ -0,0 +1,43 @@
#!/bin/sh
# Entrypoint for the `backup` sidecar. Prefers the live, bind-mounted copy of
# backup-cron.sh at /scripts (so it can be edited without a rebuild), and
# falls back to the copy baked into this image at build time if that bind
# mount is missing, empty, or stale.
#
# Why this exists: the previous entrypoint ran `/bin/sh /scripts/backup-cron.sh`
# directly. If that file wasn't there -- e.g. because the host directory
# backing the ./scripts bind mount hadn't been populated by whatever deploy
# process manages this stack -- the container failed instantly, and
# `restart: unless-stopped` retried in a tight crash loop forever: fast enough
# that the container was never "running" long enough for `docker exec` or
# Portainer's console to attach. That made the failure itself undiagnosable
# from inside the container -- you could only ever see it in the logs, and
# only by getting lucky with timing. This wrapper guarantees something always
# runs, and that the container always stays reachable, even in the worst case.
set -u
LIVE="/scripts/backup-cron.sh"
FALLBACK="/app/scripts-default/backup-cron.sh"
if [ -f "$LIVE" ]; then
echo "[entrypoint] using live scripts from /scripts (bind mount present)"
exec /bin/sh "$LIVE"
fi
echo "[entrypoint] WARNING: $LIVE not found." >&2
echo "[entrypoint] The ./scripts bind mount is missing, empty, or stale on the host." >&2
echo "[entrypoint] Check the directory backing that mount (see docker-compose.yml)." >&2
if [ -f "$FALLBACK" ]; then
echo "[entrypoint] Falling back to the scripts baked into this image at build time." >&2
echo "[entrypoint] Backups will still run, on whatever version was current when this" >&2
echo "[entrypoint] image was last built -- not any newer live edits to ./scripts." >&2
exec /bin/sh "$FALLBACK"
fi
echo "[entrypoint] FATAL: no backup-cron.sh in the bind mount or the image." >&2
echo "[entrypoint] Staying up (idle) instead of crash-looping, so this container" >&2
echo "[entrypoint] can still be reached via 'docker exec' / the Portainer console." >&2
while true; do
sleep 3600
done