| .. | ||
| promote-sync.sh | ||
| README.md | ||
Server Sync Plan
Goal
Create a server-side Bash script that promotes files uploaded with WinSCP from a staging area in the user home directory into the live site tree, while keeping the live file ownership and permissions intact.
Constraints
- The remote server is FreeBSD.
- Direct SSH-based sync from the local machine is unreliable because of the server banner and client compatibility issues.
- Uploads can only be done with WinSCP into the user-owned area.
- Final deployment into the live site requires elevation.
- The first version should cover these paths only:
wwwrusWEB-INF/classes
RUSuppercase should be ignored.
Deployment Model
Use a two-step flow:
- Upload changed files with WinSCP into a staging tree under
/home/marco/regalamiunsorriso. - Log into the server and run a Bash script with
sudoto promote the staged files into/home/sites/regalamiunsorriso.
This avoids client-side SSH problems and keeps all privileged filesystem changes on the server.
Proposed Directory Layout
Staging root:
/home/marco/regalamiunsorriso/
Live root:
/home/sites/regalamiunsorriso/
Recommended staging layout:
/home/marco/regalamiunsorriso/
incoming/
www/
rus/
WEB-INF/
classes/
logs/
bin/
Recommended script location:
/home/marco/regalamiunsorriso/bin/promote-sync.sh
First-Version Scope
The first script should be intentionally conservative.
It should:
- Process only files staged under
incoming/www,incoming/rus, andincoming/WEB-INF/classes. - Refuse to touch paths outside the live root.
- Ignore
RUSuppercase completely. - Update existing live files in place.
- Create parent directories only when they already exist in the allowed live trees or when their ownership and mode policy is explicit.
- Write a deployment log with timestamp, source path, destination path, and result.
It should not, in version 1:
- Delete live files.
- Try to sync the whole tree automatically.
- Touch
WEB-INF/lib. - Invent permissions for brand new files unless a clear policy is defined.
Why Existing Files First
Keeping the original permissions intact is easy to guarantee for files that already exist in the live tree, because the script can read the current owner, group, and mode from the destination and reapply them after the content update.
New files are a separate problem. For them, there is no original metadata to preserve. The safe approach is:
- either block them in version 1,
- or allow them only through an explicit manifest with declared owner, group, and mode.
Permission Strategy
For each staged file that maps to an existing live file:
- Read current live metadata.
- Copy the staged content to a temporary file under the destination directory.
- Apply the original owner, group, and mode to the temporary file.
- Atomically replace the live file.
- Optionally verify that metadata still matches the original values.
On FreeBSD, metadata can be read with stat -f.
Example values to capture:
owner=$(stat -f '%Su' "$dest")
group=$(stat -f '%Sg' "$dest")
mode=$(stat -f '%Lp' "$dest")
Then reapply with:
chown "$owner:$group" "$tmp"
chmod "$mode" "$tmp"
mv -f "$tmp" "$dest"
This approach is safer than blindly using cp -p, because the script explicitly preserves the metadata already present on the live file.
Safety Rules
The script should fail closed.
Required safeguards:
-
set -euo pipefail -
hardcoded allowed roots only
-
path normalization before use
-
reject symlinks in the staging area for version 1
-
reject any path containing
.. -
reject destinations that do not resolve under
/home/sites/regalamiunsorriso -
dry-run mode
-
per-file logging
-
summary at the end with counts for updated, skipped, and failed files
-
Exclude these symlinked subdirectories and internal
wwwtrees:mypics,mypics-archivio,mypics2,www/_img/,www/_news/, andwww/_tmp/.
Suggested Script Interface
./promote-sync.sh --dry-run
./promote-sync.sh --apply
./promote-sync.sh --apply --only www
./promote-sync.sh --apply --only rus
./promote-sync.sh --apply --only WEB-INF/classes
Suggested options:
--dry-run: print actions without changing files--apply: execute the promotion--only <scope>: limit to one allowed subtree--log <file>: write to a specific log file--allow-new <manifest>: optional future extension for approved new files
Recommended Promotion Algorithm
- Set fixed values for
STAGE_ROOTandLIVE_ROOT. - Build the list of allowed staging subtrees.
- Walk staged files only.
- For each staged file:
- map it to the live destination
- validate the destination is inside the live root
- verify the destination exists for version 1
- capture current owner, group, and mode from the live file
- copy staged content into a temporary file in the destination directory
- apply captured metadata
- replace the live file atomically
- log success or failure
- Print a final summary.
- Leave staged files in place unless an explicit cleanup option is requested.
Logging Format
Plain text is enough for version 1.
Example:
2026-03-28T21:10:03Z APPLY www/index.jsp -> /home/sites/regalamiunsorriso/www/index.jsp OK
2026-03-28T21:10:05Z APPLY rus/admin/menu/edit.jsp -> /home/sites/regalamiunsorriso/rus/admin/menu/edit.jsp OK
2026-03-28T21:10:06Z SKIP WEB-INF/classes/new.properties reason=destination-missing
Operational Workflow
- Prepare changed files locally.
- Upload only those files with WinSCP into the mirrored staging tree under
/home/marco/regalamiunsorriso/incoming. - Log into the server.
- Run a dry run first.
- Review the output.
- Run the apply mode with
sudo. - Verify the live site.
- Archive or clean the staged files only after validation.
Open Decisions Before Script Implementation
These should be fixed before writing version 1 of the script:
- Whether Bash is available as
/usr/bin/env bashor needs an absolute FreeBSD package path such as/usr/local/bin/bash. - Whether
sudocan run the script directly as root or only specific commands. - Whether brand new files should be blocked entirely or allowed through a manifest.
- Where logs should live permanently.
- Whether there are any directories inside
wwworrusthat must never be deployed from staging.
Proposed Implementation Phases
Phase 1
- Write the promotion script.
- Support dry run and apply mode.
- Support existing-file updates only.
- Support
www,rus, andWEB-INF/classes. - Add logging and path safety checks.
Phase 2
- Add manifest support for approved new files.
- Add optional cleanup or archive of deployed staged files.
- Add per-scope execution and better summaries.
Phase 3
- Add optional checksum comparison to skip unchanged content.
- Add rollback support via timestamped backup copies.
- Add a small local helper to prepare the WinSCP staging tree from a list of changed files.
Recommended Next Artifact
The next file to create should be the actual server script in sync/promote-sync.sh, based directly on the rules above.
Initial-Copy Mode and Permission Database
Version 1 must also include a conservative --init-sync mode that copies the live site into the user staging area and records the original live-file metadata into a permission database. This achieves two goals:
- lets
marcoreceive a full copy of the trees for local editing with readable files; - captures original owner/group/mode so the promotion step can reapply them exactly.
Placement and format
- Permission database path (suggested):
/home/marco/regalamiunsorriso/metadata/perms.jsonl(newline-delimited JSON). Store a backup as/home/marco/regalamiunsorriso/metadata/perms.jsonl.bakbefore each init-sync. - Each record should contain:
relpath,owner,group,mode(octal string),type(file|dir), and optionalsha256.
Example record:
{"relpath":"www/index.jsp","owner":"www","group":"www","mode":"0644","type":"file","sha256":"..."}
Init-sync behavior
- Validate the script is run with sufficient privilege (the script should be invoked with
sudofor this mode). - Build the list of allowed live subtrees (only
www,rus,WEB-INF/classes). - Walk the live tree and for each file/dir:
- record metadata into the
perms.jsonlfile - copy the content into the staging
incoming/tree preserving relative paths - after copy,
chown marco:marcoand set permissive modes somarcocan read and edit (files0644, dirs0755) — this provides a safe editable copy for WinSCP uploads
- Ensure symlinked exclusions (e.g.,
mypics*) are skipped during copy.
Promotion behavior (apply step uses the DB)
- When promoting a staged file back to live, the script looks up the matching
relpathinperms.jsonl. - If a record is found, the script will:
- write the staged content to a temporary file under the destination directory
- apply the recorded
owner:groupandmodeto the temporary file - atomically move the temporary file over the live file
- If no record exists for a path and
--allow-newis not used, the script should skip the file and log adestination-missing-permsreason.
Security and sanity
- The
--init-syncoperation must be explicit and dangerous-only: require--apply-initor a clear confirmation step. - Keep a timestamped copy of the permission DB for quick audits and possible rollbacks.
- Validate the
owner:groupvalues read from the live system are valid accounts on the target system before applying them during promotion.
Small example usages
# create staging and record current live metadata (requires sudo)
sudo ./promote-sync.sh --init-sync --perms-db /home/marco/regalamiunsorriso/metadata/perms.jsonl --apply-init
# dry-run promotion using the recorded perms
./promote-sync.sh --dry-run --perms-db /home/marco/regalamiunsorriso/metadata/perms.jsonl
# actual promotion
sudo ./promote-sync.sh --apply --perms-db /home/marco/regalamiunsorriso/metadata/perms.jsonl
Notes
- This design keeps all privileged operations on the server and ensures files copied for local editing are readable by
marcowhile preserving authoritative live permissions for when changes are promoted. - Later phases may add ACL support or an allowlist manifest to permit well-audited new files to be created on the live site with declared metadata.