Compare commits
3 Commits
main
...
931c03ba93
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
931c03ba93 | ||
|
|
4b7fa4565e | ||
|
|
79daa88311 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ dist/
|
|||||||
.env
|
.env
|
||||||
.DS_Store
|
.DS_Store
|
||||||
plugins/**/.DS_Store
|
plugins/**/.DS_Store
|
||||||
|
.env.local
|
||||||
|
|||||||
230
mailcow_plugin_runtime_v0_3_0_setup_v4.sh
Normal file
230
mailcow_plugin_runtime_v0_3_0_setup_v4.sh
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Re-exec under bash when invoked with sh.
|
||||||
|
if [ -z "${BASH_VERSION:-}" ]; then
|
||||||
|
if command -v bash >/dev/null 2>&1; then
|
||||||
|
exec bash "$0" "$@"
|
||||||
|
else
|
||||||
|
echo "This script requires bash." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# mailcow-plugin-runtime v0.3.0 bootstrap helper
|
||||||
|
#
|
||||||
|
# Purpose:
|
||||||
|
# - clean and stabilize the git repo
|
||||||
|
# - prepare a v0.2.0 baseline commit/tag
|
||||||
|
# - create a v0.3.0 feature branch
|
||||||
|
# - scaffold Mailcow nginx integration, runtime bootstrap wiring,
|
||||||
|
# hook renderer placeholders, and ownCloud attach plugin placeholders
|
||||||
|
#
|
||||||
|
# Default mode is DRY RUN. Nothing changes unless --apply is passed.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bash mailcow_plugin_runtime_v0_3_0_setup.sh
|
||||||
|
# bash mailcow_plugin_runtime_v0_3_0_setup.sh --apply
|
||||||
|
# bash mailcow_plugin_runtime_v0_3_0_setup.sh --apply --skip-git
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - This script is intentionally conservative.
|
||||||
|
# - It creates scaffolding and placeholders, not a fully working Mailcow patch.
|
||||||
|
# - Review every generated file before committing and pushing.
|
||||||
|
|
||||||
|
APPLY=0
|
||||||
|
SKIP_GIT=0
|
||||||
|
SKIP_SCAFFOLD=0
|
||||||
|
FEATURE_BRANCH="feature/mailcow-plugin-runtime-v0.3.0"
|
||||||
|
BASELINE_TAG="v0.2.0"
|
||||||
|
BASELINE_COMMIT_MSG="chore: clean repo and prepare plugin runtime baseline"
|
||||||
|
SCAFFOLD_COMMIT_MSG="feat: scaffold v0.3.0 mailcow integration and plugin hook structure"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--apply) APPLY=1 ;;
|
||||||
|
--skip-git) SKIP_GIT=1 ;;
|
||||||
|
--skip-scaffold) SKIP_SCAFFOLD=1 ;;
|
||||||
|
--feature-branch) FEATURE_BRANCH="${2:?missing value}"; shift ;;
|
||||||
|
--baseline-tag) BASELINE_TAG="${2:?missing value}"; shift ;;
|
||||||
|
*)
|
||||||
|
echo "Unknown argument: $1" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
say() { printf '\n[%s] %s\n' "INFO" "$*"; }
|
||||||
|
warn() { printf '\n[%s] %s\n' "WARN" "$*"; }
|
||||||
|
run() {
|
||||||
|
if [[ "$APPLY" -eq 1 ]]; then
|
||||||
|
printf '+ %s\n' "$*"
|
||||||
|
eval "$*"
|
||||||
|
else
|
||||||
|
printf '[dry-run] %s\n' "$*"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_repo() {
|
||||||
|
git rev-parse --show-toplevel >/dev/null 2>&1 || {
|
||||||
|
echo "This script must be run inside a git repository." >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_root() {
|
||||||
|
git rev-parse --show-toplevel
|
||||||
|
}
|
||||||
|
|
||||||
|
append_line_once() {
|
||||||
|
local file="$1"
|
||||||
|
local line="$2"
|
||||||
|
if [[ -f "$file" ]] && grep -Fqx "$line" "$file"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ "$APPLY" -eq 1 ]]; then
|
||||||
|
printf '%s\n' "$line" >> "$file"
|
||||||
|
else
|
||||||
|
printf '[dry-run] append to %s: %s\n' "$file" "$line"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
write_file_if_missing() {
|
||||||
|
local path="$1"
|
||||||
|
local content="$2"
|
||||||
|
if [[ -e "$path" ]]; then
|
||||||
|
say "Keeping existing file: $path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ "$APPLY" -eq 1 ]]; then
|
||||||
|
mkdir -p "$(dirname "$path")"
|
||||||
|
python3 - <<'PY' "$path" "$content"
|
||||||
|
import sys
|
||||||
|
path, content = sys.argv[1], sys.argv[2]
|
||||||
|
with open(path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
PY
|
||||||
|
else
|
||||||
|
printf '[dry-run] create file: %s\n' "$path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_gitignore() {
|
||||||
|
local file=".gitignore"
|
||||||
|
if [[ ! -f "$file" && "$APPLY" -eq 1 ]]; then
|
||||||
|
: > "$file"
|
||||||
|
fi
|
||||||
|
append_line_once "$file" "node_modules/"
|
||||||
|
append_line_once "$file" ".DS_Store"
|
||||||
|
append_line_once "$file" "dist/"
|
||||||
|
append_line_once "$file" ".env"
|
||||||
|
append_line_once "$file" ".env.local"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_index() {
|
||||||
|
if git ls-files --stage | awk '{print $1" "$4}' | grep -q '^160000 mailcow_plugin_project$'; then
|
||||||
|
run "git rm --cached mailcow_plugin_project"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git ls-files --error-unmatch node_modules >/dev/null 2>&1; then
|
||||||
|
run "git rm -r --cached node_modules"
|
||||||
|
else
|
||||||
|
say "node_modules is not tracked."
|
||||||
|
fi
|
||||||
|
|
||||||
|
local ds
|
||||||
|
while IFS= read -r ds; do
|
||||||
|
[[ -z "$ds" ]] && continue
|
||||||
|
run "git rm --cached '$ds'"
|
||||||
|
done < <(git ls-files | grep -E '(^|/)\.DS_Store$' || true)
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_commit() {
|
||||||
|
local message="$1"
|
||||||
|
if git diff --cached --quiet && git diff --quiet; then
|
||||||
|
say "No changes detected for commit: $message"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
run "git add -A"
|
||||||
|
run "git commit -m '$message'"
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_tag() {
|
||||||
|
local tag="$1"
|
||||||
|
if git rev-parse "$tag" >/dev/null 2>&1; then
|
||||||
|
say "Tag already exists: $tag"
|
||||||
|
else
|
||||||
|
run "git tag -a '$tag' -m 'mailcow plugin runtime $tag baseline'"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_branch() {
|
||||||
|
local branch="$1"
|
||||||
|
local current
|
||||||
|
current="$(git branch --show-current)"
|
||||||
|
if [[ "$current" == "$branch" ]]; then
|
||||||
|
say "Already on branch: $branch"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if git show-ref --verify --quiet "refs/heads/$branch"; then
|
||||||
|
run "git checkout '$branch'"
|
||||||
|
else
|
||||||
|
run "git checkout -b '$branch'"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
scaffold_files() {
|
||||||
|
write_file_if_missing "deploy/nginx/mailcow-plugin-runtime.conf" "# Mailcow plugin runtime reverse proxy\n# Include this from the Mailcow nginx site or merge into the active server block.\n# Adjust upstream address if the runtime is not on localhost:4110.\n\nlocation /plugins-runtime/ {\n proxy_pass http://127.0.0.1:4110/;\n proxy_http_version 1.1;\n\n proxy_set_header Host \$host;\n proxy_set_header X-Real-IP \$remote_addr;\n proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \$scheme;\n\n proxy_request_buffering off;\n proxy_buffering off;\n client_max_body_size 100m;\n}\n"
|
||||||
|
|
||||||
|
write_file_if_missing "docs/v0.3.0-mailcow-integration.md" "# mailcow-plugin-runtime v0.3.0 integration plan\n\n## Scope\n- Proxy runtime behind Mailcow at `/plugins-runtime/`\n- Load runtime bootstrap into Mailcow UI\n- Render compose toolbar and attachment hooks\n- Wire `owncloud-attach` plugin end to end\n\n## Deployment notes\n1. Start runtime on port `4110`.\n2. Add `deploy/nginx/mailcow-plugin-runtime.conf` to the Mailcow nginx server block.\n3. Ensure `/plugins-runtime/runtime/bootstrap.js` is reachable from Mailcow.\n4. Inject the bootstrap loader into the Mailcow compose page.\n\n## Expected follow-up\n- Replace placeholders in `src/integrations/mailcow` with actual selectors and injection logic.\n- Implement attachment token exchange and ownCloud file listing here.\n"
|
||||||
|
|
||||||
|
write_file_if_missing "src/integrations/mailcow/bootstrap-loader.ts" 'export interface MailcowBootstrapOptions {\n runtimeBaseUrl?: string;\n}\n\nexport function loadPluginRuntimeBootstrap(options: MailcowBootstrapOptions = {}): HTMLScriptElement {\n const runtimeBaseUrl = options.runtimeBaseUrl ?? "/plugins-runtime";\n const script = document.createElement("script");\n script.src = `\${runtimeBaseUrl}/runtime/bootstrap.js`;\n script.async = true;\n script.dataset.runtimeBaseUrl = runtimeBaseUrl;\n document.head.appendChild(script);\n return script;\n}\n'
|
||||||
|
|
||||||
|
write_file_if_missing "src/integrations/mailcow/dom-targets.ts" 'export const MAILCOW_SELECTORS = {\n composeToolbar: "[data-mailcow-compose-toolbar], .compose-toolbar, .toolbar",\n composeAttachments: "[data-mailcow-compose-attachments], .compose-attachments, .attachments",\n};\n'
|
||||||
|
|
||||||
|
write_file_if_missing "src/integrations/mailcow/install.ts" 'import { loadPluginRuntimeBootstrap } from "./bootstrap-loader";\nimport { mountComposeToolbarHook, mountComposeAttachmentsHook } from "../hooks/mailcow-hooks";\n\nexport function installMailcowPluginRuntime(): void {\n loadPluginRuntimeBootstrap();\n\n window.addEventListener("DOMContentLoaded", () => {\n mountComposeToolbarHook();\n mountComposeAttachmentsHook();\n });\n}\n'
|
||||||
|
|
||||||
|
write_file_if_missing "src/hooks/mailcow-hooks.ts" 'import { MAILCOW_SELECTORS } from "../integrations/mailcow/dom-targets";\n\nfunction ensureHookContainer(selector: string, id: string): HTMLElement | null {\n const host = document.querySelector(selector);\n if (!host) return null;\n\n let node = document.getElementById(id);\n if (!node) {\n node = document.createElement("div");\n node.id = id;\n node.dataset.pluginHookContainer = id;\n host.appendChild(node);\n }\n\n return node;\n}\n\nexport function mountComposeToolbarHook(): void {\n ensureHookContainer(MAILCOW_SELECTORS.composeToolbar, "mailcow-plugin-hook-compose-toolbar");\n}\n\nexport function mountComposeAttachmentsHook(): void {\n ensureHookContainer(MAILCOW_SELECTORS.composeAttachments, "mailcow-plugin-hook-compose-attachments");\n}\n'
|
||||||
|
|
||||||
|
write_file_if_missing "plugins/owncloud-attach/README.md" "# owncloud-attach plugin\n\n## Goal\nAdd an ownCloud file picker to the Mailcow compose flow.\n\n## Planned behavior\n- Toolbar button opens picker\n- Picker lists the authenticated user's ownCloud files\n- Selection returns attachment metadata to the runtime\n- Runtime injects attachment entries into Mailcow compose UI\n"
|
||||||
|
|
||||||
|
write_file_if_missing "plugins/owncloud-attach/plugin.json" "{\n \"id\": \"owncloud-attach\",\n \"version\": \"0.3.0\",\n \"name\": \"owncloud-attach\",\n \"hooks\": [\n \"compose.toolbar\",\n \"compose.attachments\"\n ]\n}\n"
|
||||||
|
|
||||||
|
write_file_if_missing "plugins/owncloud-attach/ui/index.html" '<!doctype html>\n<html lang="en">\n <head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n <title>owncloud-attach</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 24px; background: #0b0b0f; color: #f7f7f8; }\n .card { max-width: 720px; margin: 0 auto; padding: 24px; border-radius: 16px; background: #15151d; }\n button { padding: 10px 14px; border-radius: 10px; border: 0; cursor: pointer; }\n </style>\n </head>\n <body>\n <div class="card">\n <h1>owncloud-attach</h1>\n <p>Placeholder UI for the ownCloud picker modal.</p>\n <button id="connect-owncloud">Connect ownCloud</button>\n </div>\n </body>\n</html>\n'
|
||||||
|
|
||||||
|
write_file_if_missing "plugins/owncloud-attach/api/README.md" "# owncloud-attach API placeholders\n\nExpected endpoints:\n- GET /plugins/owncloud-attach/api/files\n- POST /plugins/owncloud-attach/api/attach\n- GET /plugins/owncloud-attach/api/health\n\nImplement the runtime-side token exchange and ownCloud file listing here.\n"
|
||||||
|
|
||||||
|
write_file_if_missing "scripts/next-steps.sh" "#!/usr/bin/env bash\nset -euo pipefail\n\necho 'Next manual steps:'\necho '1. Wire deploy/nginx/mailcow-plugin-runtime.conf into the Mailcow nginx server block.'\necho '2. Make Mailcow load src/integrations/mailcow/install.ts output on compose pages.'\necho '3. Replace placeholder selectors in src/integrations/mailcow/dom-targets.ts.'\necho '4. Build owncloud-attach picker API and file attachment flow.'\n"
|
||||||
|
|
||||||
|
if [[ "$APPLY" -eq 1 ]]; then
|
||||||
|
chmod +x scripts/next-steps.sh || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
require_repo
|
||||||
|
local root
|
||||||
|
root="$(repo_root)"
|
||||||
|
say "Repository root: $root"
|
||||||
|
say "Mode: $([[ "$APPLY" -eq 1 ]] && echo APPLY || echo DRY RUN)"
|
||||||
|
|
||||||
|
if [[ "$SKIP_GIT" -eq 0 ]]; then
|
||||||
|
say "Stabilizing git baseline"
|
||||||
|
ensure_gitignore
|
||||||
|
cleanup_index
|
||||||
|
maybe_commit "$BASELINE_COMMIT_MSG"
|
||||||
|
maybe_tag "$BASELINE_TAG"
|
||||||
|
ensure_branch "$FEATURE_BRANCH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$SKIP_SCAFFOLD" -eq 0 ]]; then
|
||||||
|
say "Scaffolding v0.3.0 integration files"
|
||||||
|
scaffold_files
|
||||||
|
maybe_commit "$SCAFFOLD_COMMIT_MSG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
say "Done. Review changes with: git status && git diff --stat"
|
||||||
|
warn "This script does not push changes. Push manually after review."
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user