Project

General

Profile

Git Workflow » History » Version 10

Redmine Admin, 11/03/2026 06:16 PM
deploy-publish.sh

1 4 Redmine Admin
*Chapter 3.1 of the dmz-web documentation · headings are numbered like the cheat sheets · index: [[Wiki]]*
2
3
{{>toc}}
4
5 2 Redmine Admin
Repos are bare, owned by the restricted host user `git` (git-shell) — the one
6 1 Redmine Admin
deliberate exception to the no-host-packages rule. Redmine only *reads* them.
7
8
**Who does what — this is the part that causes confusion, so read it first:**
9
10 2 Redmine Admin
| Step                                   | Run as          | Why                                                        |
11
|----------------------------------------|-----------------|------------------------------------------------------------|
12
| create bare repo, ACL, SELinux label   | **root**        | `/srv/git` is root/git territory                           |
13
| `git add` / `git commit` in a working copy | **owner user** (websvcmaster) | keeps `.git/objects` owned by that user          |
14
| 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 |
15
| `git push` by hand                     | **root**        | push needs `sudo -u git git-receive-pack`; websvcmaster has **no sudo** and never will |
16
| register repo in Redmine               | Redmine admin UI |                                                           |
17 1 Redmine Admin
18 2 Redmine Admin
websvcmaster's password is **locked** on purpose (`passwd -l`, set by
19
`01-bootstrap-root.sh`). Enter it only via `su - websvcmaster` from root — never
20
set a password for it. If a command run as websvcmaster asks for a password, it
21
is a root command run in the wrong shell: `exit` to root and run it there.
22 1 Redmine Admin
(`passwd -S websvcmaster` must show `L`.)
23 2 Redmine Admin
24 1 Redmine Admin
Repos: `mds-dark.git` (WordPress theme, project "MDS / MIMMAR Web Page Design"),
25
`dmz-web-deploy.git` (this host's `~/deploy`, project "dmz-web Infrastructure").
26
27 4 Redmine Admin
### 3.1.1 How Redmine reads them
28 1 Redmine Admin
29 2 Redmine Admin
```ini
30 1 Redmine Admin
# site-redmine.container
31
Volume=/srv/git:/srv/git:ro
32
Environment=GIT_CONFIG_COUNT=1
33
Environment=GIT_CONFIG_KEY_0=safe.directory
34
Environment=GIT_CONFIG_VALUE_0=*
35
```
36
37 2 Redmine Admin
Rootless UID mapping: container uid *n* → host uid **200000 + n − 1**; Redmine
38
runs as uid 999 → host **200998** (`podman top site-redmine user huser`).
39 1 Redmine Admin
One-time host prep (root):
40
41
```bash
42
semanage fcontext -a -t container_file_t '/srv/git(/.*)?'
43
restorecon -Rv /srv/git
44
```
45
46 4 Redmine Admin
### 3.1.2 New bare repo — root
47 1 Redmine Admin
48 2 Redmine Admin
```bash
49 1 Redmine Admin
R=/srv/git/<repo>.git
50
sudo -u git git init --bare $R
51
sudo -u git git -C $R symbolic-ref HEAD refs/heads/main   # bare HEAD defaults to master; every repo here uses main
52
setfacl -R  -m u:200998:rX $R                             # Redmine read access
53
setfacl -R -d -m u:200998:rX $R                           # ...also for files future pushes create
54
restorecon -Rv $R      # "not reset as customized by admin" on every file = already container_file_t = OK
55
git config --global --add safe.directory /home/<user>/<workdir>   # lets root push from that working copy
56
```
57
58 4 Redmine Admin
### 3.1.3 Working copy, first time — owner user (`su - <user>` from root)
59 1 Redmine Admin
60 2 Redmine Admin
```bash
61 1 Redmine Admin
git config --global user.name  "<user>"                   # once per user, or commit refuses
62
git config --global user.email "sysadmin@mdsdefense.com"  # matching a Redmine user's email links commits to them
63
cd <workdir>
64
git init -b main
65
# .gitignore first: no *.env, *secret*, *password*, *.key/*.pem/*.crt, dumps, archives, data/, tmp/
66
git add -A && git status --short
67
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
68
git commit -m "initial import $(date +%F)"
69
git remote add origin /srv/git/<repo>.git
70
git log --oneline
71
exit                                                      # back to root
72
```
73
74 4 Redmine Admin
### 3.1.4 Push — root
75 1 Redmine Admin
76
First push of a new working copy (once):
77
78 2 Redmine Admin
```bash
79 1 Redmine Admin
cd /home/<user>/<workdir>
80
git push --receive-pack='sudo -u git git-receive-pack' -u origin main
81
chown -R <user>:<user> .git          # root's push leaves root-owned files in .git
82
```
83
84
Expected: `* [new branch] main -> main`. Verify: `sudo -u git git -C /srv/git/<repo>.git log --oneline`.
85
After that, never push by hand — use `deploy-commit.sh` (E).
86
87 4 Redmine Admin
### 3.1.5 Redmine
88 1 Redmine Admin
89 2 Redmine Admin
Project → Settings → Repositories → New repository: SCM **Git**, Main repository
90
✓, Identifier `<repo>`, Path `/srv/git/<repo>.git`, Report last commit ✓ → Create.
91
Open the Repository tab; the commit must be listed. If not:
92
`podman exec site-redmine git -C /srv/git/<repo>.git log --oneline` —
93 4 Redmine Admin
"dubious ownership" → safe.directory env missing; "permission denied" → ACL (step 3.1.2).
94 1 Redmine Admin
95 4 Redmine Admin
### 3.1.6 Day-to-day — `deploy-commit.sh` (root)
96 1 Redmine Admin
97 2 Redmine Admin
One root command does the whole cycle in the right identities: `git add -A`
98
and `git commit` **as websvcmaster** (identity auto-set), a guard that refuses
99
to commit staged lines that look like literal credentials, `git push`
100
**as root** via `sudo -u git git-receive-pack`, optional annotated tag, and
101
`chown` of `.git` back to websvcmaster. Source: `root-bin/deploy-commit.sh` in
102 1 Redmine Admin
the repo; installed as `/usr/local/sbin/deploy-commit.sh` (0750).
103
104 2 Redmine Admin
```bash
105 1 Redmine Admin
deploy-commit.sh -n                         # dry run: shows what is staged, commits nothing
106
deploy-commit.sh -m "what changed"          # commit + push
107
deploy-commit.sh -m "cheat sheet v1.1" -t cheatsheet-1.1   # ...+ tag
108
deploy-commit.sh -t cheatsheet-1.1          # nothing to commit: tag HEAD, push the tag
109
deploy-commit.sh -y -m "..."                # skip the credential guard after checking the flagged lines
110
deploy-commit.sh -h
111
```
112
113 2 Redmine Admin
Editing still happens as websvcmaster (`su - websvcmaster`, edit, `exit`);
114
`deploy-commit.sh` never edits files, only `.git`. The guard flags staged lines of
115
the form `<…password|secret|token|api_key|app_key…> = <literal of 8+ chars>`;
116 1 Redmine Admin
variables (`$X`), placeholders (`<…>`, `...`), Quadlet `Secret=` lines and
117 2 Redmine Admin
comments are ignored. If it fires: move the value to a Podman secret or
118
`.gitignore` the file and re-run; if the lines are harmless, re-run with `-y`. Install/update the script:
119 1 Redmine Admin
120 2 Redmine Admin
```bash
121
install -o root -g root -m 0750 /home/websvcmaster/deploy/root-bin/deploy-commit.sh /usr/local/sbin/deploy-commit.sh
122
```
123 1 Redmine Admin
124 4 Redmine Admin
### 3.1.7 Publishing finished work — `deploy-publish.sh` (root)
125 1 Redmine Admin
126 2 Redmine Admin
Replaces the manual "copy from `/root` into the repo, fix owner, commit, tag, type the wiki into
127
Redmine" cycle. A delivery is **one file**, `dmz-web-release-<version>.zip` (`<version>` = the cheat sheet
128 7 Redmine Admin
version of that release), dropped into `/root` (with the `pf-app-<x.y.z>.tar.gz` it names, if it carries an
129
application version); run `deploy-publish.sh` as root. Since 1.6 it **shows the whole plan first and asks one
130
question**; nothing changes before the answer. It calls `deploy-commit.sh` for the git part, so E still applies.
131 1 Redmine Admin
132 2 Redmine Admin
The package holds the changed files (scripts, the cheat sheet), a `wiki/` folder (pages + png),
133 7 Redmine Admin
`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:
134 1 Redmine Admin
135 2 Redmine Admin
0. **Package:** unpacked into a fresh private directory; paths are checked (no `..`, absolute paths
136
   or symlinks), the file name must match `RELEASE`, `SHA256SUMS` must verify, and a file that is not
137 7 Redmine Admin
   listed in `SHA256SUMS` rejects 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 **newer `deploy-publish.sh`** than the running one, the plan is made by the package's copy (verified by `SHA256SUMS`) 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).
138 1 Redmine Admin
1. **Repo:** each known file is compared by its **version header** with the repo / installed copy:
139
   newer → installed (repo files owned by websvcmaster, `/usr/local/sbin` copies root 0750);
140 2 Redmine Admin
   identical → nothing to do; **older, same version with different content, or no version header →
141
   refused** (stops a stale copy overwriting a newer one; `-f` overrides). Known files: cheat sheet,
142 1 Redmine Admin
   `snipeit-install.sh`, `add-wordpress-site.sh`, `redmine-install.sh`, `check-sites.sh`,
143 7 Redmine Admin
   `verify-host-deps.sh`, `deploy-commit.sh`, `deploy-publish.sh`, `as-web`, `pf-upgrade.sh`, `pf-install.sh`, `pf-mgmt-cheatsheet.md`,
144
   `tools/build-release.sh` — add a line to the `ITEMS` table in the script for another one. `releases/*.md` of the package are copied to `releases/` 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; `-f` overrides) unless `RELEASE` names it on its `amends:` line (a later release correcting an earlier note on purpose).
145
   **Then the rest of the plan:** the lines added to `releases/CHECKSUMS.txt` (the package's sha256 and the lines of `pf-app.sha256`), the PF application (the newest version `pf-app.sha256` names, if it is newer than the unit and its tarball is in `/root` with that sha256: the **dry run of `pf-upgrade.sh`** is printed), the wiki pages (read-only check: *would create / update / unchanged*) and older release zips that go to `deploy-done` with this one. Then **one question**: *apply this plan?* (`-n` stops before it; `-Y -s <sha256>` applies without asking).
146
2. **Commit:** the planned lines are appended to `releases/CHECKSUMS.txt` (a zip cannot hold its own hash; `pf-upgrade.sh` reads the tarball's sha256 from there); `deploy-commit.sh -m "release <version>: <files and versions>"`; a cheat sheet install also
147 6 Redmine Admin
   gets the tag `cheatsheet-<version>` (skipped if that tag exists).
148 2 Redmine Admin
3. **Wiki:** `wiki/*.md` → Redmine REST API: page created or updated (parent `Wiki`, the duplicate
149 1 Redmine Admin
   `# title` first line dropped, `.png` attached once, unchanged pages skipped). `@@COMMIT@@` in a page
150 7 Redmine Admin
   (the Releases table) becomes the commit made in step 2 (on a re-run: the commit of the tag `cheatsheet-<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).
151 1 Redmine Admin
4. **Tidy:** after a complete, clean run the package is **moved** (never deleted) to
152 7 Redmine Admin
   `/root/deploy-done/<timestamp>/`, together with older `dmz-web-release-*.zip` still in `/root`. With refusals, errors, `-n`, `-R` or `-W` it stays in `/root`; running it
153 2 Redmine Admin
   again is safe, handled files then show as *identical* and are skipped.
154 7 Redmine Admin
5. **PF:** after a clean publish, `pf-upgrade.sh -y -s <sha256> <version>` upgrades the application (§5.7) — the run the plan showed. `-P` leaves it out (run `pf-upgrade.sh <version>` later; it finds the sha256 in `releases/CHECKSUMS.txt`).
155 2 Redmine Admin
156 1 Redmine Admin
Loose files (no package) still work: put them into `/root` (and `dmz-web-wiki-pages.zip` for the wiki)
157 2 Redmine Admin
and the handled ones are moved to `/root/deploy-done/<timestamp>/`.
158
159
```bash
160 7 Redmine Admin
deploy-publish.sh -n -s <sha256>  # dry run: the plan, wiki read-only GETs, nothing changed or moved, no question
161
deploy-publish.sh -s <sha256>     # the plan, then "apply this plan?" -- the normal run (newest /root/dmz-web-release-*.zip)
162
deploy-publish.sh -Y -s <sha256>  # the plan and apply it without asking (unattended)
163
deploy-publish.sh -P          # do not upgrade the PF application at the end
164 6 Redmine Admin
deploy-publish.sh -R          # wiki only            deploy-publish.sh -W   # repo only
165 2 Redmine Admin
deploy-publish.sh -m "msg"    # own commit message   deploy-publish.sh -y   # skip deploy-commit's guard
166
deploy-publish.sh -f          # install even when older / equal / no version header
167 5 Redmine Admin
deploy-publish.sh --status    # read-only overview: published / installed / waiting (see below)
168 1 Redmine Admin
```
169 5 Redmine Admin
170
**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.
171 1 Redmine Admin
172 2 Redmine Admin
One-time setup for the wiki step: `deploy-publish.sh --init` writes `/root/.deploy-publish.conf`
173
(mode 0600; set `RM_PROJECT` to the project **identifier**, Project settings → Identifier); put the
174 1 Redmine Admin
Redmine API key (My account → API access key) into `/root/.redmine-api-key`, `chmod 600`; Redmine
175
Administration → Settings → API → *Enable REST web service*. Requests go to NPM on this host
176
(`--resolve track.mdsdefense.com:444:127.0.0.1`), like `check-sites.sh`. The page text in Redmine is
177
overwritten by the package's text — the first real run replaces pages that were typed in by hand, so
178
start with `-n`. Bootstrap the first time (the script cannot install itself before it exists):
179
180
```bash
181
install -o root -g root -m 0750 /root/deploy-publish.sh /usr/local/sbin/deploy-publish.sh
182 2 Redmine Admin
deploy-publish.sh --init && deploy-publish.sh -n
183
```
184
185
Afterwards the script updates itself from the package (the running copy is replaced safely).
186 7 Redmine Admin
187
**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
188
189
```bash
190 10 Redmine Admin
tools/build-release.sh -v 1.25 -b dmz-web-release-1.24.zip -b dmz-web-release-1.23.zip -b ~/deploy -a pf-app-1.1.15.tar.gz  <tree>
191 7 Redmine Admin
```
192
193 8 Redmine Admin
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).
194 2 Redmine Admin
195 4 Redmine Admin
### 3.1.8 Recovering from the usual mistakes
196 1 Redmine Admin
197
* `fatal: could not open '.git/COMMIT_EDITMSG': Permission denied` (or any
198 2 Redmine Admin
  permission error on commit) → root touched `.git` (committed there, or pushed/tagged
199
  without `deploy-commit.sh`). Fix: `chown -R <user>:<user> /home/<user>/<workdir>/.git`,
200
  then use `deploy-commit.sh` from now on (it chowns every time). Also check root's `~/.gitconfig` didn't pick up the user's identity.
201
* `Everything up-to-date` on push right after you expected a new commit → the
202
  commit didn't happen (identity error or permission error scrolled past); `git log --oneline`
203
  and `git status --short` in the working copy. A tag made at that moment points at the
204
  old commit: `deploy-commit.sh -t <tag>` after the real commit (it force-moves the tag).
205 4 Redmine Admin
* `error: src refspec main does not match any` → no commit yet on `main` (step 3.1.3 not done).
206 2 Redmine Admin
* `error: key does not contain a section: receivepack` → wrong syntax; use
207 4 Redmine Admin
  `--receive-pack=` (step 3.1.4), not `-c receivepack=`.
208 2 Redmine Admin
* `sudo: a password is required` / password prompt → you are websvcmaster; this is a root step (`exit`, then `deploy-commit.sh`).
209 4 Redmine Admin
* Redmine shows the repo empty → bare HEAD still points to `master` (step 3.1.2 `symbolic-ref`).
210 2 Redmine Admin
* `deploy-publish.sh`: `REFUSED: no version header` → the copy is an old/unversioned one; fetch the current file or `-f` if you are sure. `checksum mismatch` / `does not match RELEASE` → the package is damaged or was renamed; get it again. `HTTP 401/403` on the wiki step → API key wrong, REST not enabled, or the account may not edit this project's wiki. `HTTP 404` for every page → wrong `RM_PROJECT` (it must be the identifier, not the project name).
211 1 Redmine Admin
212
---
213 3 Redmine Admin
_Source: `dmz-web-podman-cheatsheet.md` v1.5 in the `dmz-web-deploy` repo ([[Releases]]). Edit the wiki and the repo copy together._