Chapter 3.1 of the dmz-web documentation · headings are numbered like the cheat sheets · index: Wiki
- Table of contents
- 3.1.1 How Redmine reads them
- 3.1.2 New bare repo — root
- 3.1.3 Working copy, first time — owner user (su - <user> from root)
- 3.1.4 Push — root
- 3.1.5 Redmine
- 3.1.6 Day-to-day — deploy-commit.sh (root)
- 3.1.7 Publishing finished work — deploy-publish.sh (root)
- 3.1.8 Recovering from the usual mistakes
Repos are bare, owned by the restricted host user git (git-shell) — the one
deliberate exception to the no-host-packages rule. Redmine only reads them.
Who does what — this is the part that causes confusion, so read it first:
| Step | Run as | Why |
|---|---|---|
| create bare repo, ACL, SELinux label | root |
/srv/git is root/git territory |
git add / git commit in a working copy |
owner user (websvcmaster) | keeps .git/objects owned by that user |
| commit + push, day to day |
root, via deploy-commit.sh
|
it commits as websvcmaster and pushes as root itself — the only command you normally type |
git push by hand |
root | push needs sudo -u git git-receive-pack; websvcmaster has no sudo and never will |
| register repo in Redmine | Redmine admin UI |
websvcmaster's password is locked on purpose (passwd -l, set by
01-bootstrap-root.sh). Enter it only via su - websvcmaster from root — never
set a password for it. If a command run as websvcmaster asks for a password, it
is a root command run in the wrong shell: exit to root and run it there.
(passwd -S websvcmaster must show L.)
Repos: mds-dark.git (WordPress theme, project "MDS / MIMMAR Web Page Design"),
dmz-web-deploy.git (this host's ~/deploy, project "dmz-web Infrastructure").
3.1.1 How Redmine reads them¶
# site-redmine.container
Volume=/srv/git:/srv/git:ro
Environment=GIT_CONFIG_COUNT=1
Environment=GIT_CONFIG_KEY_0=safe.directory
Environment=GIT_CONFIG_VALUE_0=*
Rootless UID mapping: container uid n → host uid 200000 + n − 1; Redmine
runs as uid 999 → host 200998 (podman top site-redmine user huser).
One-time host prep (root):
semanage fcontext -a -t container_file_t '/srv/git(/.*)?'
restorecon -Rv /srv/git
3.1.2 New bare repo — root¶
R=/srv/git/<repo>.git
sudo -u git git init --bare $R
sudo -u git git -C $R symbolic-ref HEAD refs/heads/main # bare HEAD defaults to master; every repo here uses main
setfacl -R -m u:200998:rX $R # Redmine read access
setfacl -R -d -m u:200998:rX $R # ...also for files future pushes create
restorecon -Rv $R # "not reset as customized by admin" on every file = already container_file_t = OK
git config --global --add safe.directory /home/<user>/<workdir> # lets root push from that working copy
3.1.3 Working copy, first time — owner user (su - <user> from root)¶
git config --global user.name "<user>" # once per user, or commit refuses
git config --global user.email "sysadmin@mdsdefense.com" # matching a Redmine user's email links commits to them
cd <workdir>
git init -b main
# .gitignore first: no *.env, *secret*, *password*, *.key/*.pem/*.crt, dumps, archives, data/, tmp/
git add -A && git status --short
grep -nEi 'password|secret|token|key_base' -r . --exclude-dir=.git | grep -viE 'Secret=|podman secret|\$\{?[A-Z_]*(PASSWORD|SECRET)' # must be comments/prompts only
git commit -m "initial import $(date +%F)"
git remote add origin /srv/git/<repo>.git
git log --oneline
exit # back to root
3.1.4 Push — root¶
First push of a new working copy (once):
cd /home/<user>/<workdir>
git push --receive-pack='sudo -u git git-receive-pack' -u origin main
chown -R <user>:<user> .git # root's push leaves root-owned files in .git
Expected: * [new branch] main -> main. Verify: sudo -u git git -C /srv/git/<repo>.git log --oneline.
After that, never push by hand — use deploy-commit.sh (E).
3.1.5 Redmine¶
Project → Settings → Repositories → New repository: SCM Git, Main repository
✓, Identifier <repo>, Path /srv/git/<repo>.git, Report last commit ✓ → Create.
Open the Repository tab; the commit must be listed. If not:
podman exec site-redmine git -C /srv/git/<repo>.git log --oneline —
"dubious ownership" → safe.directory env missing; "permission denied" → ACL (step 3.1.2).
3.1.6 Day-to-day — deploy-commit.sh (root)¶
One root command does the whole cycle in the right identities: git add -A
and git commit as websvcmaster (identity auto-set), a guard that refuses
to commit staged lines that look like literal credentials, git push
as root via sudo -u git git-receive-pack, optional annotated tag, and
chown of .git back to websvcmaster. Source: root-bin/deploy-commit.sh in
the repo; installed as /usr/local/sbin/deploy-commit.sh (0750).
deploy-commit.sh -n # dry run: shows what is staged, commits nothing
deploy-commit.sh -m "what changed" # commit + push
deploy-commit.sh -m "cheat sheet v1.1" -t cheatsheet-1.1 # ...+ tag
deploy-commit.sh -t cheatsheet-1.1 # nothing to commit: tag HEAD, push the tag
deploy-commit.sh -y -m "..." # skip the credential guard after checking the flagged lines
deploy-commit.sh -h
Editing still happens as websvcmaster (su - websvcmaster, edit, exit);
deploy-commit.sh never edits files, only .git. The guard flags staged lines of
the form <…password|secret|token|api_key|app_key…> = <literal of 8+ chars>;
variables ($X), placeholders (<…>, ...), Quadlet Secret= lines and
comments are ignored. If it fires: move the value to a Podman secret or
.gitignore the file and re-run; if the lines are harmless, re-run with -y. Install/update the script:
install -o root -g root -m 0750 /home/websvcmaster/deploy/root-bin/deploy-commit.sh /usr/local/sbin/deploy-commit.sh
3.1.7 Publishing finished work — deploy-publish.sh (root)¶
Replaces the manual "copy from /root into the repo, fix owner, commit, tag, type the wiki into
Redmine" cycle. A delivery is one file, dmz-web-release-<version>.zip (<version> = the cheat sheet
version of that release), dropped into /root (with the pf-app-<x.y.z>.tar.gz it names, if it carries an
application version); run deploy-publish.sh as root. Since 1.6 it shows the whole plan first and asks one
question; nothing changes before the answer. It calls deploy-commit.sh for the git part, so E still applies.
The package holds the changed files (scripts, the cheat sheet), a wiki/ folder (pages + png),
RELEASE (version, build date, optionally amends: <note> ...) and SHA256SUMS, and optionally a releases/ folder with the notes of that release (what changed, steps, what to look at, rollback) and pf-app.sha256 (the sha256 of the application tarballs, which travel as separate files). Packages are built with tools/build-release.sh, which refuses a file older than the published one (the stale pf-install.sh of release 1.21 would have stopped there). What the script does:
-
Package: unpacked into a fresh private directory; paths are checked (no
.., absolute paths
or symlinks), the file name must matchRELEASE,SHA256SUMSmust verify, and a file that is not
listed inSHA256SUMSrejects the package. Then the sha256 of the package itself is printed; with-s <sha256>(the value from the delivery note) the run stops on a mismatch. If the package carries a newerdeploy-publish.shthan the running one, the plan is made by the package's copy (verified bySHA256SUMS) and it is installed together with everything else after the answer (1.5 still installed the new copy first; from 1.6 on nothing changes before the question). -
Repo: each known file is compared by its version header with the repo / installed copy:
newer → installed (repo files owned by websvcmaster,/usr/local/sbincopies root 0750);
identical → nothing to do; older, same version with different content, or no version header →
refused (stops a stale copy overwriting a newer one;-foverrides). Known files: cheat sheet,
snipeit-install.sh,add-wordpress-site.sh,redmine-install.sh,check-sites.sh,
verify-host-deps.sh,deploy-commit.sh,deploy-publish.sh,as-web,pf-upgrade.sh,pf-install.sh,pf-mgmt-cheatsheet.md,
tools/build-release.sh— add a line to theITEMStable in the script for another one.releases/*.mdof the package are copied toreleases/in the repo: a new note is installed, an identical one is skipped, a note that exists with other content is refused (release notes are history;-foverrides) unlessRELEASEnames it on itsamends:line (a later release correcting an earlier note on purpose).
Then the rest of the plan: the lines added toreleases/CHECKSUMS.txt(the package's sha256 and the lines ofpf-app.sha256), the PF application (the newest versionpf-app.sha256names, if it is newer than the unit and its tarball is in/rootwith that sha256: the dry run ofpf-upgrade.shis printed), the wiki pages (read-only check: would create / update / unchanged) and older release zips that go todeploy-donewith this one. Then one question: apply this plan? (-nstops before it;-Y -s <sha256>applies without asking). -
Commit: the planned lines are appended to
releases/CHECKSUMS.txt(a zip cannot hold its own hash;pf-upgrade.shreads the tarball's sha256 from there);deploy-commit.sh -m "release <version>: <files and versions>"; a cheat sheet install also
gets the tagcheatsheet-<version>(skipped if that tag exists). -
Wiki:
wiki/*.md→ Redmine REST API: page created or updated (parentWiki, the duplicate
# titlefirst line dropped,.pngattached once, unchanged pages skipped).@@COMMIT@@in a page
(the Releases table) becomes the commit made in step 2 (on a re-run: the commit of the tagcheatsheet-<version>; a page that needs a hash and has none is skipped, never overwritten with the placeholder);commit:@@TAG:<tag>@@becomes the commit of that tag (the row of the release before, so no hash is typed by hand). -
Tidy: after a complete, clean run the package is moved (never deleted) to
/root/deploy-done/<timestamp>/, together with olderdmz-web-release-*.zipstill in/root. With refusals, errors,-n,-Ror-Wit stays in/root; running it
again is safe, handled files then show as identical and are skipped. -
PF: after a clean publish,
pf-upgrade.sh -y -s <sha256> <version>upgrades the application (§5.7) — the run the plan showed.-Pleaves it out (runpf-upgrade.sh <version>later; it finds the sha256 inreleases/CHECKSUMS.txt).
Loose files (no package) still work: put them into /root (and dmz-web-wiki-pages.zip for the wiki)
and the handled ones are moved to /root/deploy-done/<timestamp>/.
deploy-publish.sh -n -s <sha256> # dry run: the plan, wiki read-only GETs, nothing changed or moved, no question
deploy-publish.sh -s <sha256> # the plan, then "apply this plan?" -- the normal run (newest /root/dmz-web-release-*.zip)
deploy-publish.sh -Y -s <sha256> # the plan and apply it without asking (unattended)
deploy-publish.sh -P # do not upgrade the PF application at the end
deploy-publish.sh -R # wiki only deploy-publish.sh -W # repo only
deploy-publish.sh -m "msg" # own commit message deploy-publish.sh -y # skip deploy-commit's guard
deploy-publish.sh -f # install even when older / equal / no version header
deploy-publish.sh --status # read-only overview: published / installed / waiting (see below)
What is published, installed and waiting? deploy-publish.sh --status answers it and changes nothing: the version of every file in the repo and in /usr/local/sbin (a difference is flagged), HEAD and the last cheatsheet-* tag, uncommitted or unpushed commits, release packages and pf-app-*.tar.gz waiting in /root (flagged NOT PUBLISHED YET when newer than the last tag), and for PF the Image= tag in the repo unit, in the installed unit and of the running site-pf container. Lines starting with !! are the things to look at.
One-time setup for the wiki step: deploy-publish.sh --init writes /root/.deploy-publish.conf
(mode 0600; set RM_PROJECT to the project identifier, Project settings → Identifier); put the
Redmine API key (My account → API access key) into /root/.redmine-api-key, chmod 600; Redmine
Administration → Settings → API → Enable REST web service. Requests go to NPM on this host
(--resolve track.mdsdefense.com:444:127.0.0.1), like check-sites.sh. The page text in Redmine is
overwritten by the package's text — the first real run replaces pages that were typed in by hand, so
start with -n. Bootstrap the first time (the script cannot install itself before it exists):
install -o root -g root -m 0750 /root/deploy-publish.sh /usr/local/sbin/deploy-publish.sh
deploy-publish.sh --init && deploy-publish.sh -n
Afterwards the script updates itself from the package (the running copy is replaced safely).
Building a package (wherever releases are put together; root is not needed): the files go into one directory (scripts and cheat sheets at the top, wiki/, releases/, pf-app.sha256, and AMENDS listing earlier notes this release corrects), then
tools/build-release.sh -v 1.26 -b dmz-web-release-1.25.zip -b dmz-web-release-1.24.zip -b ~/deploy -a pf-app-1.1.16.tar.gz <tree>
compares every file with the highest version found in the baselines (published zips, the repo working copy) and stops on an older copy, a changed file with an unchanged version, a missing version header, a changed note not named in AMENDS, a cheat sheet version other than -v, or a shell script that fails bash -n; with -a <pf-app tarball> it also runs the app's label check (app/i18n.py check: every key the page uses is in strings.csv, placeholders agree in every language) and checks that the tarball's sha256 is in pf-app.sha256; it never overwrites an existing zip (a package is never re-issued under the same name).
3.1.8 Recovering from the usual mistakes¶
-
fatal: could not open '.git/COMMIT_EDITMSG': Permission denied(or any
permission error on commit) → root touched.git(committed there, or pushed/tagged
withoutdeploy-commit.sh). Fix:chown -R <user>:<user> /home/<user>/<workdir>/.git,
then usedeploy-commit.shfrom now on (it chowns every time). Also check root's~/.gitconfigdidn't pick up the user's identity. -
Everything up-to-dateon push right after you expected a new commit → the
commit didn't happen (identity error or permission error scrolled past);git log --oneline
andgit status --shortin the working copy. A tag made at that moment points at the
old commit:deploy-commit.sh -t <tag>after the real commit (it force-moves the tag). -
error: src refspec main does not match any→ no commit yet onmain(step 3.1.3 not done). -
error: key does not contain a section: receivepack→ wrong syntax; use
--receive-pack=(step 3.1.4), not-c receivepack=. -
sudo: a password is required/ password prompt → you are websvcmaster; this is a root step (exit, thendeploy-commit.sh). - Redmine shows the repo empty → bare HEAD still points to
master(step 3.1.2symbolic-ref). -
deploy-publish.sh:REFUSED: no version header→ the copy is an old/unversioned one; fetch the current file or-fif you are sure.checksum mismatch/does not match RELEASE→ the package is damaged or was renamed; get it again.HTTP 401/403on the wiki step → API key wrong, REST not enabled, or the account may not edit this project's wiki.HTTP 404for every page → wrongRM_PROJECT(it must be the identifier, not the project name).
Source: dmz-web-podman-cheatsheet.md v1.5 in the dmz-web-deploy repo (Releases). Edit the wiki and the repo copy together.
Updated by Redmine Admin 2 days ago · 11 revisions