Privacy model added, plus some helper scripts
This commit is contained in:
parent
9b9c182dc7
commit
79a207a6f2
|
|
@ -0,0 +1,20 @@
|
|||
ca4806cd3e7827b9666e6929cbcae8f3e2eba87e499f7dd7008d8edf2fcc774e staging/calendar-plugin/calendar-plugin.php
|
||||
1daa5861c0d10258c0d16c5c88c1a18fb3c8df7b590f7facfe7c28f678643bdb staging/calendar-plugin/src/Contracts/AuthAdapterInterface.php
|
||||
25cff4b0fc2ee292b53c152edd083c6af66200a25eec28f8dfce37d126a8892d staging/calendar-plugin/src/Contracts/DatabaseAdapterInterface.php
|
||||
4f0f4caa5ac98499854336f5b74af55ce889653f3956e5df10910f869a23fdba staging/calendar-plugin/src/Contracts/HttpAdapterInterface.php
|
||||
15e8f58c7360d6cd0c76c945abfeb026f8278d40b330e99b67955eb2f85f5563 staging/calendar-plugin/src/Contracts/OptionsAdapterInterface.php
|
||||
47d55e845b8a696c55fa86f597bb2760b6e35d974b70f365593d38697ef398c8 staging/calendar-plugin/src/Domain/CalDavService.php
|
||||
4dc3337761c97aa550896fcc377aaab8338c598f39e489742dacbfd20e1a71b1 staging/calendar-plugin/src/Domain/EventService.php
|
||||
412a22ecd910535c7ace2549a86eacf08cc9cd824f1767e60116d8593355f57f staging/calendar-plugin/src/Domain/IcsService.php
|
||||
5f7fb1f8c00136ad2c4a2dc8b909dabf6494a194b09aa73557a4bf68d73f4ed2 staging/calendar-plugin/src/Domain/RecurrenceExpander.php
|
||||
20ee26671fd934f36e97606cfd2ec1d5101ac8064594989bc955bf805fae2502 staging/calendar-plugin/src/Domain/SettingsService.php
|
||||
9956790a5d62f5798f3ec15fc507fb04f33e01fd27969db09e91a6d1b41cd33f staging/calendar-plugin/src/Domain/UserService.php
|
||||
5aaac066919b60461bde2e96cbfb4de66a5a28e89d2a61b78f0e2d346f23395b staging/calendar-plugin/src/Infrastructure/ServiceContainer.php
|
||||
70334df8ca06c8fe61d81f24cb0d8f19285180f9950054bd86af0083adf8b4c8 staging/calendar-plugin/src/Infrastructure/WordPress/MigrationManager.php
|
||||
8e6c95e9e1c051606e66d95cf0bcf92b2ca087bc491f32ab4921e0898cf77b81 staging/calendar-plugin/src/Infrastructure/WordPress/WordPressAuthAdapter.php
|
||||
68c0ca15ad2c8b6363a2578b85f8daf0d3a094e612a120a2cdd2a2bfd8fe5e3c staging/calendar-plugin/src/Infrastructure/WordPress/WordPressDatabaseAdapter.php
|
||||
8da85db3c1e69c2c5f01aaa2f558aa8f0323446d8af0aec5b34d4607cd4afe1b staging/calendar-plugin/src/Infrastructure/WordPress/WordPressHttpAdapter.php
|
||||
cf9fddcecb07af2c03ad2c0e448be12a6b45dd936efc8bc1fd46a52b4af864ea staging/calendar-plugin/src/Infrastructure/WordPress/WordPressOptionsAdapter.php
|
||||
ffc7c3eef8f3ed0873b5f614765219925d07c24ffd6ffa7422a3d862a7342450 staging/calendar-plugin/src/Plugin.php
|
||||
4e6930c79d9ce1045be6863ddd7546e19f7ef7e839d41a41014a3efb3183eaf0 staging/calendar-plugin/src/bootstrap.php
|
||||
893c6df62beed87a981d372c473e5012d1b5d1c254d23b39cda44ae8a08cd16c staging/calendar-plugin/uninstall.php
|
||||
Binary file not shown.
|
|
@ -36,7 +36,7 @@ This document covers:
|
|||
### Create Event
|
||||
- `POST /events`
|
||||
- Requires calendar write capability.
|
||||
- Request body supports fields in `requirements/editor.md`.
|
||||
- Request body supports fields in `requirements/editor.md`, including `visibility` (`public`/`private`).
|
||||
- Response: `201` with created event payload and identifiers.
|
||||
|
||||
### List Events
|
||||
|
|
@ -70,6 +70,16 @@ This document covers:
|
|||
- Must not split the underlying recurring series.
|
||||
- Response: `204` on success.
|
||||
|
||||
|
||||
## Public Read Endpoints and Privacy Redaction
|
||||
|
||||
Public-read endpoints (for example `/public/events`, `/public/sidebar-events`, and ICS feed generation paths) must apply privacy masking for `private` events:
|
||||
|
||||
- Event date/time placement remains unchanged.
|
||||
- Title is replaced with `Private Event`.
|
||||
- Description/location/category and other non-time content are omitted from public responses.
|
||||
- Logged-in write-capable UI and authenticated CalDAV/API reads continue to receive full event details.
|
||||
|
||||
## CalDAV User Workflow Endpoints
|
||||
|
||||
### Register
|
||||
|
|
@ -139,3 +149,4 @@ Acceptance should verify:
|
|||
- Validation and error payloads are consistent.
|
||||
- Single-occurrence delete creates recurrence exception rather than split series.
|
||||
- Authz rules enforce role/access constraints.
|
||||
- Privacy redaction rules are enforced consistently across public API payloads and ICS output.
|
||||
|
|
|
|||
|
|
@ -40,9 +40,15 @@ This document covers:
|
|||
|
||||
### ICS Access
|
||||
- Public ICS endpoint visibility is governed by setup configuration.
|
||||
- If endpoint is public: `public` may read only approved public events.
|
||||
- If endpoint is public: `public` may read public events and redacted private events.
|
||||
- If endpoint requires auth: enforce configured auth policy consistently.
|
||||
|
||||
### Privacy Visibility Rules
|
||||
- `public` viewers (not logged in) see full details for public events.
|
||||
- `public` viewers see private events in normal date/time positions with title `Private Event` and no other event content.
|
||||
- Logged-in write-capable users and authenticated CalDAV users may view full details for both public and private events.
|
||||
- Privacy masking applies consistently across public calendar page views, sidebar, and public ICS output.
|
||||
|
||||
## Ownership and Scope Rules
|
||||
- CalDAV users operate against the single shared `public` calendar collection.
|
||||
- Access control is enforced by account approval state (`pending_approval` vs `active`) rather than per-user private calendars.
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ The endpoint must support these operations at minimum.
|
|||
### Create and Update
|
||||
- `PUT`: create new event resource or replace an existing event resource.
|
||||
- `PUT` updates must preserve recurrence/exception semantics from iCalendar input.
|
||||
- CalDAV clients (including Thunderbird) must be able to read current event privacy and update it on edit (for example via `CLASS`).
|
||||
- Write operations must require approved authenticated access.
|
||||
|
||||
### Delete
|
||||
|
|
@ -101,6 +102,12 @@ Minimum mapping expectations:
|
|||
- Recurrence rules -> `RRULE`
|
||||
- Recurrence exceptions -> `EXDATE` and/or additional `VEVENT` with matching `UID` plus `RECURRENCE-ID`
|
||||
|
||||
Privacy visibility mapping:
|
||||
|
||||
- Event visibility must round-trip through CalDAV/iCalendar semantics (for example `CLASS:PUBLIC` and `CLASS:PRIVATE` where supported).
|
||||
- Authenticated CalDAV users must receive full event content for both public and private events.
|
||||
- CalDAV feeds are not subject to the public redaction model used by anonymous web/ICS surfaces.
|
||||
|
||||
Recurrence behavior:
|
||||
|
||||
- Series-level recurrence remains a single logical event sequence keyed by `UID`.
|
||||
|
|
@ -188,3 +195,4 @@ Acceptance should verify:
|
|||
- Sync collection reports provide incremental changes.
|
||||
- Recurring-event single-occurrence delete results in exception representation, not sequence split.
|
||||
- Returned iCalendar validates against RFC 5545 expectations and is accepted by target clients.
|
||||
- Privacy values round-trip through CalDAV edit flows (including Thunderbird) without losing event detail fidelity.
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ If a report is unsupported for a resource, server returns standards-appropriate
|
|||
|
||||
## Content Handling
|
||||
- Event objects use `text/calendar` payloads with RFC 5545-compatible `VCALENDAR`.
|
||||
- Authenticated CalDAV object responses (`GET`, `REPORT`, `calendar-multiget`) return full event details for both public and private events.
|
||||
- Unsupported component types should be rejected unless explicitly mapped.
|
||||
- Server should normalize line endings/content as required by iCalendar compatibility.
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ Requirements:
|
|||
The stored model must support these fields (direct columns or normalized equivalents):
|
||||
|
||||
- Event identifier (internal primary key)
|
||||
- `visibility` (`public` or `private`)
|
||||
- `title`
|
||||
- `location`
|
||||
- `category`
|
||||
|
|
@ -55,6 +56,7 @@ The stored model must support these fields (direct columns or normalized equival
|
|||
- Data model must support efficient retrieval by date range for day/week/month/year UI windows.
|
||||
- Data model must support generation of recurrence occurrences for both web UI and ICS output.
|
||||
- Queries for public UI must return only intended public event data.
|
||||
- Queries for public UI/ICS/sidebar must include private events with privacy redaction (time/date shown, non-time content masked).
|
||||
|
||||
## Local Testing Data Requirements
|
||||
The local compatibility-harness test environment must support deterministic data setup and teardown.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ This document covers:
|
|||
Required fields:
|
||||
- `id` (PK)
|
||||
- `uid` (stable iCalendar UID)
|
||||
- `visibility` (`public`, `private`; default `public`)
|
||||
- `title`
|
||||
- `description`
|
||||
- `location`
|
||||
|
|
@ -42,6 +43,11 @@ Constraints:
|
|||
- `title`, `start_datetime` required
|
||||
- `end_datetime >= start_datetime`
|
||||
- recurrence fields internally consistent
|
||||
- `visibility` must be one of the supported enum values
|
||||
|
||||
Privacy semantics:
|
||||
- `private` events are stored with full details.
|
||||
- Public-facing renderers (public ICS/public calendar/sidebar) must apply redaction rules at read time, not by destructively altering stored event data.
|
||||
|
||||
### Recurrence Exceptions
|
||||
Required fields:
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ Behavior requirements:
|
|||
Supported fields:
|
||||
|
||||
- `title` (required)
|
||||
- `visibility` (`public` or `private`; default `public`)
|
||||
- `description`
|
||||
- `location`
|
||||
- `category`
|
||||
|
|
@ -55,6 +56,7 @@ UI behavior requirements:
|
|||
- Time controls hide when `all_day_event` is selected.
|
||||
- Floating/persistent labels keep field purpose visible.
|
||||
- Internal event ID is hidden from users.
|
||||
- Editor must provide a control to mark events as `public` or `private`.
|
||||
|
||||
## Recurrence Model
|
||||
Supported `repeat_type` values:
|
||||
|
|
@ -91,3 +93,4 @@ Acceptance should verify:
|
|||
- Deleting one occurrence creates an exception while preserving the series.
|
||||
- Monthly ordinal patterns save and reload correctly.
|
||||
- Validation blocks invalid start/end ranges.
|
||||
- Privacy visibility setting saves and reloads correctly.
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ Field mapping requirements:
|
|||
- Plugin end date/time -> `DTEND`
|
||||
- Plugin category -> `CATEGORIES`
|
||||
- Stable unique event identifier -> `UID`
|
||||
- Plugin `visibility` controls public-output masking for private events
|
||||
- Last update timestamp -> `DTSTAMP`
|
||||
|
||||
Additional mapping guidance:
|
||||
|
|
@ -57,6 +58,15 @@ Additional mapping guidance:
|
|||
- Timed events must use `DTSTART;TZID=Europe/London` and `DTEND;TZID=Europe/London`.
|
||||
- `DTEND` must represent a valid end boundary and not precede `DTSTART`.
|
||||
|
||||
|
||||
Privacy behavior:
|
||||
|
||||
- `public` events are exported with normal mapped content.
|
||||
- `private` events remain present in the feed at normal date/time positions, but with redaction:
|
||||
- `SUMMARY` must be `Private Event`
|
||||
- `DTSTART`/`DTEND` remain populated
|
||||
- `DESCRIPTION`, `LOCATION`, and `CATEGORIES` must be omitted from public ICS output
|
||||
|
||||
## Recurrence Mapping
|
||||
Recurring entries should be represented with `RRULE` where possible.
|
||||
|
||||
|
|
@ -84,7 +94,7 @@ If specific recurrence shapes cannot be represented in a single RRULE, plugin ma
|
|||
- Generation errors should fail safely with observable error handling.
|
||||
|
||||
## Security and Privacy
|
||||
- Export must include only events intended for public/user-facing distribution.
|
||||
- Export must enforce visibility policy: private events are included with redacted content in public ICS output.
|
||||
- Internal/admin-only metadata must not leak into ICS properties.
|
||||
|
||||
## Verification Requirements
|
||||
|
|
|
|||
|
|
@ -26,13 +26,14 @@ This document covers:
|
|||
|
||||
## Access Policy
|
||||
- Access mode is configurable in `Setup`:
|
||||
- `public_read`: endpoint accessible without auth, only public events included
|
||||
- `public_read`: endpoint accessible without auth; includes both public and private events, with private events redacted
|
||||
- `authenticated_read`: endpoint requires authenticated access
|
||||
- Default mode: `public_read` unless overridden by policy requirements.
|
||||
|
||||
## Data Scope
|
||||
- ICS output includes only events intended for the selected endpoint audience.
|
||||
- Internal/admin-only metadata must never appear in output.
|
||||
- In `public_read` mode, private events remain in timeline position but expose only date/time and the title `Private Event`.
|
||||
- Recurrence and exception semantics follow `requirements/ics.md` and `requirements/recurrence_exceptions.md`.
|
||||
|
||||
## Stability and Subscription
|
||||
|
|
|
|||
|
|
@ -83,6 +83,16 @@ Minimum display requirements:
|
|||
- Category (if provided)
|
||||
- Description excerpt/summary (if configured for display)
|
||||
|
||||
Privacy display rules:
|
||||
|
||||
- Public events render full details per normal display rules.
|
||||
- Private events remain visible in chronological position for all viewers.
|
||||
- In public (not logged-in) UI and sidebar rendering, private events must display:
|
||||
- Title as `Private Event`
|
||||
- Date/time fields as normal
|
||||
- No location/category/description content
|
||||
- In logged-in write-capable UI, private events display full details.
|
||||
|
||||
## Web Event Editing UX (Authorized Users)
|
||||
Where the viewer has write permission, the web UI must support event creation/editing.
|
||||
|
||||
|
|
@ -94,6 +104,7 @@ Requirements:
|
|||
- Irrelevant recurrence fields must be hidden based on current recurrence selections.
|
||||
- Form controls use persistent inline/floating labels so field purpose remains visible after input.
|
||||
- Event ID must not be shown in the UI.
|
||||
- Create/edit panel must show current privacy (`public`/`private`) and allow changing it before save.
|
||||
- `location` and `category` should be rendered on one row.
|
||||
- `start date/time` and `end date/time` should be rendered on one row.
|
||||
- `repeat type` and `every` interval should be rendered on one row.
|
||||
|
|
@ -112,6 +123,7 @@ Requirements:
|
|||
## Login and Access Modes
|
||||
- `/calendar` must support two user modes: public (not logged in) and logged-in.
|
||||
- Public mode is read-only.
|
||||
- In public mode, private events are redacted (title `Private Event`; time preserved; non-time fields hidden).
|
||||
- A `Login` action must open an in-page dialog.
|
||||
- Login dialog must support credential login.
|
||||
- Login dialog must support registration (registration is a request for approval/write access).
|
||||
|
|
@ -149,3 +161,4 @@ Acceptance should verify:
|
|||
- All five view modes are selectable and render expected event windows.
|
||||
- Empty-state behavior is clear and user-friendly.
|
||||
- ICS link is present and returns valid calendar payload.
|
||||
- Privacy redaction behavior is correct in public views, sidebar, and logged-in views.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENV_FILE="${ROOT_DIR}/credentials/.env"
|
||||
|
||||
if [[ -f "${ENV_FILE}" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "${ENV_FILE}"
|
||||
fi
|
||||
|
||||
ARTIFACT=""
|
||||
WP_ROOT="${REMOTE_WP_PATH:-/var/www/wordpress}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--artifact)
|
||||
ARTIFACT="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--wp-root)
|
||||
WP_ROOT="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--help|-h)
|
||||
cat <<'USAGE'
|
||||
Deploy a plugin artifact to remote WordPress.
|
||||
|
||||
Usage:
|
||||
scripts/deploy_remote.sh [--artifact /abs/or/relative/path.zip] [--wp-root /var/www/wordpress]
|
||||
|
||||
Defaults:
|
||||
- Artifact: latest ./package/calendar-plugin-*.zip
|
||||
- Remote host settings from credentials/.env
|
||||
|
||||
This script enforces ownership:
|
||||
- chown -R www-data:www-data <remote plugin dir>
|
||||
USAGE
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "[deploy] unknown argument: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${ARTIFACT}" ]]; then
|
||||
ARTIFACT="$(ls -1 "${ROOT_DIR}/package/calendar-plugin-"*.zip 2>/dev/null | sort -V | tail -n1 || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "${ARTIFACT}" ]]; then
|
||||
echo "[deploy] no artifact found; run scripts/package_plugin.sh first" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${ARTIFACT}" != /* ]]; then
|
||||
ARTIFACT="${ROOT_DIR}/${ARTIFACT#./}"
|
||||
fi
|
||||
|
||||
if [[ ! -f "${ARTIFACT}" ]]; then
|
||||
echo "[deploy] artifact not found: ${ARTIFACT}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MANIFEST="${ARTIFACT%.zip}.manifest.sha256"
|
||||
if [[ ! -f "${MANIFEST}" ]]; then
|
||||
echo "[deploy] warning: manifest not found beside artifact (${MANIFEST}); continuing" >&2
|
||||
fi
|
||||
|
||||
required=(REMOTE_HOST REMOTE_PORT REMOTE_USER REMOTE_SSH_KEY_PATH REMOTE_APP_DIR REMOTE_WP_CLI WP_URL)
|
||||
for key in "${required[@]}"; do
|
||||
if [[ -z "${!key:-}" ]]; then
|
||||
echo "[deploy] missing required env var: ${key}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
SSH_KEY_PATH="${REMOTE_SSH_KEY_PATH}"
|
||||
if [[ "${SSH_KEY_PATH}" != /* ]]; then
|
||||
SSH_KEY_PATH="${ROOT_DIR}/${SSH_KEY_PATH}"
|
||||
fi
|
||||
|
||||
if [[ ! -f "${SSH_KEY_PATH}" ]]; then
|
||||
echo "[deploy] ssh key not found: ${SSH_KEY_PATH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
artifact_base="$(basename -- "${ARTIFACT}")"
|
||||
stage_name="${artifact_base%.zip}"
|
||||
STAGE_DIR="/tmp/${stage_name}"
|
||||
|
||||
SSH=(ssh -F /dev/null -i "${SSH_KEY_PATH}" -p "${REMOTE_PORT}" -o StrictHostKeyChecking=accept-new "${REMOTE_USER}@${REMOTE_HOST}")
|
||||
RSYNC=(rsync -avz --delete -e "ssh -F /dev/null -i ${SSH_KEY_PATH} -p ${REMOTE_PORT} -o StrictHostKeyChecking=accept-new")
|
||||
|
||||
echo "[deploy] staging on remote: ${STAGE_DIR}"
|
||||
"${SSH[@]}" "mkdir -p '${STAGE_DIR}'"
|
||||
|
||||
if [[ -f "${MANIFEST}" ]]; then
|
||||
"${RSYNC[@]}" "${ARTIFACT}" "${MANIFEST}" "${REMOTE_USER}@${REMOTE_HOST}:${STAGE_DIR}/"
|
||||
else
|
||||
"${RSYNC[@]}" "${ARTIFACT}" "${REMOTE_USER}@${REMOTE_HOST}:${STAGE_DIR}/"
|
||||
fi
|
||||
|
||||
"${SSH[@]}" "set -euo pipefail; rm -rf '${STAGE_DIR}/extracted'; mkdir -p '${STAGE_DIR}/extracted'; unzip -q '${STAGE_DIR}/${artifact_base}' -d '${STAGE_DIR}/extracted'; test -f '${STAGE_DIR}/extracted/calendar-plugin/calendar-plugin.php'"
|
||||
"${SSH[@]}" "set -euo pipefail; rsync -a --delete '${STAGE_DIR}/extracted/calendar-plugin/' '${REMOTE_APP_DIR}/'"
|
||||
"${SSH[@]}" "set -euo pipefail; chown -R www-data:www-data '${REMOTE_APP_DIR}'"
|
||||
"${SSH[@]}" "set -euo pipefail; '${REMOTE_WP_CLI}' --path='${WP_ROOT}' plugin activate calendar-plugin --allow-root >/dev/null 2>&1 || true"
|
||||
|
||||
# Exact-match style checksum dry-run check
|
||||
"${SSH[@]}" "set -euo pipefail; rsync -avznc --delete '${STAGE_DIR}/extracted/calendar-plugin/' '${REMOTE_APP_DIR}/' >/tmp/codex_rsync_check.out; if grep -Eq '^[^./]|^\./' /tmp/codex_rsync_check.out; then cat /tmp/codex_rsync_check.out; exit 1; fi"
|
||||
|
||||
# Ownership sanity check: must be zero mismatches
|
||||
non_owned="$("${SSH[@]}" "set -euo pipefail; find '${REMOTE_APP_DIR}' \( ! -user www-data -o ! -group www-data \) | wc -l")"
|
||||
if [[ "${non_owned}" != "0" ]]; then
|
||||
echo "[deploy] ownership check failed: ${non_owned} path(s) not owned by www-data:www-data" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Resolve slug for endpoint checks
|
||||
url_slug="$("${SSH[@]}" "'${REMOTE_WP_CLI}' --path='${WP_ROOT}' option get calendar_plugin_url_slug --allow-root 2>/dev/null || true" | tr -d '\r' | tail -n1)"
|
||||
if [[ -n "${url_slug}" ]]; then
|
||||
ICS_URL="${WP_URL%/}/${url_slug}/calendar.ics"
|
||||
CALDAV_URL="${WP_URL%/}/${url_slug}/caldav/"
|
||||
else
|
||||
ICS_URL="${WP_URL%/}/calendar.ics"
|
||||
CALDAV_URL="${WP_URL%/}/caldav/"
|
||||
fi
|
||||
|
||||
health_code="$(curl -sS -o /tmp/codex_health.out -w '%{http_code}' "${WP_URL%/}/wp-json/calendar/v1/health" || true)"
|
||||
ics_code="$(curl -sS -o /tmp/codex_ics.out -w '%{http_code}' "${ICS_URL}" || true)"
|
||||
caldav_code="$(curl -sS -o /tmp/codex_caldav.out -w '%{http_code}' "${CALDAV_URL}" || true)"
|
||||
|
||||
echo "[deploy] plugin deployed: ${artifact_base}"
|
||||
echo "[deploy] ownership: www-data:www-data (verified)"
|
||||
echo "[deploy] checks: health=${health_code} ics=${ics_code} caldav_unauth=${caldav_code}"
|
||||
echo "[deploy] urls: ${ICS_URL} ${CALDAV_URL}"
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENV_FILE="${ROOT_DIR}/credentials/.env"
|
||||
|
||||
if [[ -f "${ENV_FILE}" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "${ENV_FILE}"
|
||||
fi
|
||||
|
||||
VERSION=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--version)
|
||||
VERSION="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--help|-h)
|
||||
cat <<'USAGE'
|
||||
Create a plugin package artifact from ./code.
|
||||
|
||||
Usage:
|
||||
scripts/package_plugin.sh [--version X.Y.Z]
|
||||
|
||||
Notes:
|
||||
- If --version is not provided, reads Version from code/calendar-plugin.php.
|
||||
- Writes zip + manifest to ./package.
|
||||
USAGE
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "[package] unknown argument: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${VERSION}" ]]; then
|
||||
VERSION="$(sed -n 's/^ \* Version: \(.*\)$/\1/p' "${ROOT_DIR}/code/calendar-plugin.php" | head -n1 | tr -d '[:space:]')"
|
||||
fi
|
||||
|
||||
if [[ -z "${VERSION}" ]]; then
|
||||
echo "[package] unable to detect plugin version from code/calendar-plugin.php" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SLUG="calendar-plugin"
|
||||
PACKAGE_DIR="${ROOT_DIR}/package"
|
||||
STAGING_DIR="${PACKAGE_DIR}/staging/${SLUG}"
|
||||
ARTIFACT="${PACKAGE_DIR}/${SLUG}-${VERSION}.zip"
|
||||
MANIFEST="${PACKAGE_DIR}/${SLUG}-${VERSION}.manifest.sha256"
|
||||
|
||||
mkdir -p "${PACKAGE_DIR}/staging"
|
||||
rm -rf "${STAGING_DIR}"
|
||||
mkdir -p "${STAGING_DIR}"
|
||||
|
||||
rsync -a --delete "${ROOT_DIR}/code/" "${STAGING_DIR}/"
|
||||
|
||||
(
|
||||
cd "${PACKAGE_DIR}/staging"
|
||||
zip -qr "${ARTIFACT}" "${SLUG}"
|
||||
)
|
||||
|
||||
(
|
||||
cd "${STAGING_DIR}"
|
||||
find . -type f -print0 | sort -z | xargs -0 sha256sum
|
||||
) > "${MANIFEST}"
|
||||
|
||||
echo "[package] created artifact: ${ARTIFACT}"
|
||||
echo "[package] created manifest: ${MANIFEST}"
|
||||
Loading…
Reference in New Issue