feat(deploy): add canonical Gitea recovery flows
This commit is contained in:
@@ -81,9 +81,347 @@ Supported components in this source:
|
|||||||
- `n8n-private-extension`
|
- `n8n-private-extension`
|
||||||
- `module-foundry`
|
- `module-foundry`
|
||||||
- `device-plane`
|
- `device-plane`
|
||||||
|
- `gitea`
|
||||||
- `proxy-contur`
|
- `proxy-contur`
|
||||||
- `dc-amd-proxy`
|
- `dc-amd-proxy`
|
||||||
|
|
||||||
|
## Fresh Gitea installation
|
||||||
|
|
||||||
|
`gitea` is a one-time, fresh-install-only component. Its artifact contains
|
||||||
|
only the reviewed Compose file and strict deployment descriptor; it contains
|
||||||
|
no database, repository, user, token, key, hook, runtime data or secret. Build
|
||||||
|
the deterministic artifact locally with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node infra/deploy-runner/build-gitea-fresh-install-artifact.mjs \
|
||||||
|
gitea-fresh-install-YYYYMMDD-NNN
|
||||||
|
```
|
||||||
|
|
||||||
|
The runtime is fixed to Gitea `1.27.1-rootless` by exact digest and
|
||||||
|
`linux/amd64`. The runner never pulls it: the exact image must first be loaded
|
||||||
|
through a separately reviewed trusted image-acquisition step. Compose is
|
||||||
|
invoked with `--pull never --no-deps`, uses `network_mode: none`, publishes no
|
||||||
|
ports, and serves only the host-mounted Unix socket
|
||||||
|
`/volume1/docker/nodedc-gitea/socket/gitea.sock`. It exposes no SSH port, uses
|
||||||
|
fresh SQLite, enables no LFS, and mounts two runner-generated secrets by URI.
|
||||||
|
Registration and password Basic auth are disabled, while MFA is enforced;
|
||||||
|
hooks, webhooks, migrations, packages, Actions, OAuth2/OpenID, federation and
|
||||||
|
update checks are disabled. HTTP Git with PAT remains the intended trusted
|
||||||
|
client path; PAT clone/push acceptance belongs to the later reviewed
|
||||||
|
user/repository restoration transition, not this empty installation. LFS may
|
||||||
|
only be enabled by that later transition after its secret and restart behavior
|
||||||
|
has been accepted against the pinned runtime.
|
||||||
|
|
||||||
|
Plan and apply fail closed unless all live prerequisites are already true:
|
||||||
|
|
||||||
|
- `/volume1/docker/nodedc-gitea`, the `nodedc-gitea` project, and any stale
|
||||||
|
`nodedc-gitea_internal` network are absent; TCP/4022 is closed and Docker
|
||||||
|
publishes neither TCP/3000 nor TCP/4022;
|
||||||
|
- Docker Server is exactly 24.0.2, Compose is at least 2.20.1, and the exact
|
||||||
|
pinned image ID and RepoDigest are local for linux/amd64 with image user
|
||||||
|
`1000:1000`;
|
||||||
|
- the separate root-owned Nginx bridge
|
||||||
|
`/usr/local/etc/nginx/conf.d/http.nodedc-gitea-uds.conf` matches its exact
|
||||||
|
reviewed bytes, owner/mode and SHA-256; Nginx 1.23.1 configuration validates,
|
||||||
|
and only its root/uid-1023 processes own `127.0.0.1:3000`. The bridge proxies
|
||||||
|
to the Unix socket, preserves Host/X-Real-IP/XFF/XFP, rate-limits login and
|
||||||
|
bounds concurrent requests without limiting Git request bodies;
|
||||||
|
- DSM reverse-proxy UUID `5bc46027-0307-4261-af7e-4f94a3c508c9` persistently
|
||||||
|
and in generated nginx config routes `git.dcserve.ru:443` to
|
||||||
|
`127.0.0.1:3000`; the generated vhost must also retain its exact host guard
|
||||||
|
and canonical Host/X-Real-IP/XFF/XFP forwarding directives, so the UDS
|
||||||
|
bridge never trusts a client-supplied `X-Real-IP`;
|
||||||
|
- the emergency broad INPUT DROP for TCP/3000 has been removed, while the
|
||||||
|
legacy `172.22.0.222:3000` OUTPUT DROP and TCP/4022 INPUT DROP remain;
|
||||||
|
- any legacy container named `gitea` is stopped with restart policy `no`.
|
||||||
|
|
||||||
|
Changing DSM reverse proxy/firewall rules, acquiring evidence from the old
|
||||||
|
root, restoring trusted users/repositories, and loading the pinned image are
|
||||||
|
separate reviewed transitions. This component never reads or mounts
|
||||||
|
`/volume1/docker/gitea` and never starts the legacy container. Failed apply
|
||||||
|
stops/removes only the `nodedc-gitea` candidate, proves that it is absent, and
|
||||||
|
quarantines the complete newly created root (including data/config/socket/secrets) for
|
||||||
|
evidence, and restores the absent source/root state without deleting runtime
|
||||||
|
evidence. If candidate absence cannot be proven, rollback preserves the root in
|
||||||
|
place and records reconciliation-required instead of disconnecting live bind
|
||||||
|
mounts.
|
||||||
|
|
||||||
|
Run the policy suite before publishing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
python3 infra/deploy-runner/test_gitea_fresh_install.py -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gitea incident salvage (clean-state recovery)
|
||||||
|
|
||||||
|
The additive `gitea-incident-salvage` slice is the reviewed recovery design for
|
||||||
|
incident `gitea-20260814`. It does not upgrade or mount the compromised state.
|
||||||
|
The candidate starts with a new SQLite database, new config, new runtime
|
||||||
|
secrets, new email addresses and new password hashes. The exact v2 decision
|
||||||
|
bundle is embedded as inert, digest-bound data:
|
||||||
|
|
||||||
|
- 2 new active identities (`dctouch`, admin; `SILVER`, non-admin), each with a
|
||||||
|
new root-contained one-time password and mandatory password change;
|
||||||
|
- 8 new locked identities with random discarded passwords, inactive,
|
||||||
|
restricted and prohibited from login;
|
||||||
|
- 45 exact repositories: 32 private `dctouch` repositories and 13 public
|
||||||
|
`SILVER` repositories. The other 962 legacy users and 2013 legacy repository
|
||||||
|
rows are not imported into the clean database.
|
||||||
|
|
||||||
|
The successor v3 descriptor embeds the owner-confirmed, canonical
|
||||||
|
`confirmed-disposition-v1.json` and the additive
|
||||||
|
`confirmed-closure-disposition-v1.json`. Both are bound to the exact snapshot,
|
||||||
|
database, identity decision, reference manifest, unsupported-state report,
|
||||||
|
schema catalog and semantic-topics hashes. The original disposition's 105
|
||||||
|
reference decisions are explicit rows, not namespace wildcards: 85 normal
|
||||||
|
heads, 4 tags and 4 wiki heads are eligible for later live reconstruction; the
|
||||||
|
exact 5 pull refs and 7 remote refs are sealed archive-only evidence and can
|
||||||
|
never be auto-promoted. The closure disposition authorizes only bounded
|
||||||
|
plan-time inventory and records the target policy for access, collaboration,
|
||||||
|
issue/PR metadata, attachments, releases, labels, projects, units, Packages
|
||||||
|
and Actions. Observed hashes do not become activation authority, and the new
|
||||||
|
closure-report digest is deliberately unpinned until separate review.
|
||||||
|
|
||||||
|
Build the deterministic review artifact locally with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node infra/deploy-runner/build-gitea-incident-salvage-artifact.mjs \
|
||||||
|
gitea-incident-salvage-subrelation-closure-20260814-006
|
||||||
|
```
|
||||||
|
|
||||||
|
The original v1 descriptor and `...salvage-20260814-001.tgz` remain immutable.
|
||||||
|
The exact `...salvage-disposition-20260814-002.tgz` also remains immutable and
|
||||||
|
must not be retried: its plan failed safely because it assumed every nonempty
|
||||||
|
`repository.topics` TEXT value was a JSON array. A bounded immutable-database
|
||||||
|
classification proved all 45 kept rows are instead exact four-byte canonical
|
||||||
|
JSON `null`, Gitea's semantic empty representation. The `...topics-...-003`
|
||||||
|
artifact is the forward fix with a new patch id and digest.
|
||||||
|
That exact `...topics-...-003` artifact is now also an immutable predecessor.
|
||||||
|
The local pre-audit `...closure-...-004` draft must not be staged: review found
|
||||||
|
orphan-join and nullable-Actions gaps. The local
|
||||||
|
`...closure-reviewed-...-005` artifact is also stale and must not be staged:
|
||||||
|
its primary closure was fail-closed, but it retained a broad polymorphic-
|
||||||
|
subrelation blocker. The additive `...subrelation-closure-...-006` successor
|
||||||
|
closes the concrete comment/history/merger relations in the pinned snapshot
|
||||||
|
schema and is the only artifact produced by the current builder.
|
||||||
|
The successor builder refuses to overwrite an existing artifact basename and
|
||||||
|
publishes from an exclusively created same-directory temporary file using an
|
||||||
|
atomic no-clobber hard link. A failed build removes only its private temporary
|
||||||
|
file. The disposition is sorted, compact ASCII JSON with one terminal LF.
|
||||||
|
|
||||||
|
The runtime pin is
|
||||||
|
`docker.gitea.com/gitea:1.27.2-rootless@sha256:7de5f49a…3abd2c`, with exact
|
||||||
|
linux/amd64 OCI image ID `sha256:272085a8…bc5c4c6`. It runs as `1000:1000`,
|
||||||
|
uses the direct Gitea binary, `network_mode: none`, no Docker ports, no SSH and
|
||||||
|
no LFS. Candidate restart policy is `no`. The existing reviewed Nginx bridge
|
||||||
|
cannot reach the candidate while the socket parent is mode `0700`; publication
|
||||||
|
is an explicit post-acceptance change to `0750`, followed by public TLS and
|
||||||
|
smart-HTTP checks. Only after those checks may restart policy become
|
||||||
|
`unless-stopped`. The transition never changes or enforces two-factor
|
||||||
|
authentication.
|
||||||
|
|
||||||
|
The snapshot boundary is exact: UUID
|
||||||
|
`f5a3fe3a-93ea-bb4d-847f-6221a6bcbc9f`, immutable SQLite SHA-256
|
||||||
|
`8db9e74a…1a13052`, and the 972/2058 decisions must recompute from the database
|
||||||
|
row by row. The runner enumerates only each v2 `repo_relative_path` and
|
||||||
|
`wiki_relative_path`. It rejects symlinks, special files, hard links, NOCOW,
|
||||||
|
alternates, shallow/replace/grafts/worktrees, promisor state, unknown object
|
||||||
|
material and incomplete pack/index pairs. One preloaded mount inventory also
|
||||||
|
guards the `objects` and `refs` roots plus every traversed directory and file:
|
||||||
|
all must remain on the trusted snapshot device, outside nested mountpoints and,
|
||||||
|
for directories, outside nested Btrfs subvolumes. Traversal errors fail closed.
|
||||||
|
Forbidden-path probes (`objects/info/alternates`, grafts, replace refs and the
|
||||||
|
other listed sentinels) lstat each path component without following links and
|
||||||
|
validate every ancestor before probing its child; a symlink or mount boundary
|
||||||
|
therefore stops the plan before any lookup can escape the snapshot tree.
|
||||||
|
|
||||||
|
Only three exact derived-file classes observed in the bounded 45-repository
|
||||||
|
inventory are excluded: dumb-HTTP `objects/info/packs`,
|
||||||
|
`objects/pack/pack-<oid>.bitmap` with its exact complete pack/index pair, and
|
||||||
|
monolithic `objects/info/commit-graph`. Each must be a bounded regular,
|
||||||
|
single-link, non-NOCOW file. The reference manifest records its exact path,
|
||||||
|
class and byte size, but the runner neither reads nor copies the excluded
|
||||||
|
bytes. Unobserved `.rev`, MIDX and split commit-graph layouts stay fail-closed;
|
||||||
|
`.promisor`, `.mtimes`, `.keep`, unknown and temporary lookalikes are not
|
||||||
|
classified as disposable accelerators.
|
||||||
|
|
||||||
|
The same inventory found receive-pack quarantine directories matching only an
|
||||||
|
exact direct child `objects/tmp_objdir-incoming-[A-Za-z0-9]{6}`. A match must
|
||||||
|
retain the reviewed uid/gid `1000:1000`, mode `0755`, single-link and non-NOCOW
|
||||||
|
directory metadata. Its exact path and lstat fields are recorded, then the
|
||||||
|
subtree is pruned without reading, hashing, copying or reflinking contents; the
|
||||||
|
sealed read-only incident snapshot retains the forensic copy. Any name,
|
||||||
|
metadata, mount or subvolume drift remains a hard stop. A reviewed reference/OID
|
||||||
|
manifest is required; raw HEAD/refs files are never copied. The intended data
|
||||||
|
path is same-filesystem per-file `--reflink=always`, with no byte-copy fallback
|
||||||
|
and a bounded exclusive-allocation gate.
|
||||||
|
|
||||||
|
This slice is intentionally **NO-GO for apply** at the current review point.
|
||||||
|
`plan` inventories and prints the exact ref-manifest digest, byte length and
|
||||||
|
canonical compact JSON. The JSON is the reviewable, hash-bound reference/OID
|
||||||
|
artifact: it contains approved repository identities, ref names/OIDs, object
|
||||||
|
totals and stat-bound derived/quarantine exclusions, but no Git object bytes,
|
||||||
|
hooks or config. The same plan prints the canonical unsupported-state report
|
||||||
|
and a separate incident-closure report, each with its SHA-256, byte length and
|
||||||
|
compact ASCII JSON. The closure report is evidence only: its disposition keeps
|
||||||
|
`expectedSha256` and `expectedBytes` null and retains an explicit review-pin
|
||||||
|
blocker.
|
||||||
|
|
||||||
|
The reviewed plan evidence bound by the disposition is reference manifest
|
||||||
|
`9cddaf0e…a311` (36,010 bytes), unsupported-state report
|
||||||
|
`4b2cecf8…106ac` (119,468 bytes), and schema catalog
|
||||||
|
`b5e3b677…b6db`. The report has exact 45 repository rows with empty
|
||||||
|
`schema_missing`, `schema_mismatch`, and `anomalies` arrays. These facts close
|
||||||
|
only the owner's material-state decision; fsck/reachability, physical blob
|
||||||
|
inventory, sanitized archive creation and candidate acceptance remain separate
|
||||||
|
verifiers.
|
||||||
|
|
||||||
|
The unsupported-state report has one deterministic row for every one of the
|
||||||
|
45 kept repository IDs, including explicit zeroes. It separates direct
|
||||||
|
repository-relation counts, directional base/head PR counts, repo-unit type
|
||||||
|
counts, text-metadata presence booleans and denormalized numeric hints; it does
|
||||||
|
not add overlapping relations into a misleading grand total. LFS rows are
|
||||||
|
reported per repository with validated logical byte sums and distinct-OID
|
||||||
|
aggregates, including size conflicts and sharing with non-kept repositories.
|
||||||
|
Every association sharing a kept OID must also have an integer repository ID
|
||||||
|
that resolves to the exact snapshot `repository` table; invalid and orphan
|
||||||
|
owners make shared/unique byte aggregates unavailable and add a data-anomaly
|
||||||
|
blocker.
|
||||||
|
Attachments are reported per repository with validated logical byte sums and
|
||||||
|
explicit, overlapping issue/comment/release/unlinked link splits. Neither
|
||||||
|
category claims that a physical blob exists; no title, body, note, comment,
|
||||||
|
config, URL, token, key, secret, hash payload or blob content is selected or
|
||||||
|
exported.
|
||||||
|
|
||||||
|
The report also embeds the exact `PRAGMA table_xinfo` catalog and explicit
|
||||||
|
label-to-table-to-repository-column map for a code-owned allowlist of relevant
|
||||||
|
tables. `sqlite_master` must prove an ordinary, non-virtual table before any
|
||||||
|
row query; SQLite 3.37+ adds `PRAGMA table_list` ordinary-main-table proof. On
|
||||||
|
an older compatible SQLite, the weaker object-kind capability is recorded and
|
||||||
|
adds a coverage blocker. `query_only=ON` and `trusted_schema=OFF` are read back
|
||||||
|
before the snapshot connection is accepted. Only column identifiers and
|
||||||
|
structural metadata are exported, never DDL, defaults or row values. The v3
|
||||||
|
closure inventory follows code-owned, schema-attested primary joins for the
|
||||||
|
issue/PR dependency tables (comments, assignees, content histories, labels, issue
|
||||||
|
users/watches, reactions, reviews/state, dependencies, notifications,
|
||||||
|
stopwatches, tracked time and auto-merge), plus projects/boards, releases,
|
||||||
|
attachments, Packages and Actions. It emits only IDs required to prove joins,
|
||||||
|
actor classes, counts, declared sizes and SQLite byte lengths; it never selects
|
||||||
|
or prints text bodies, names, URLs, package metadata, workflow payloads,
|
||||||
|
credentials, tokens, secrets or logs. Package and Actions target features stay
|
||||||
|
disabled and no legacy row is importable. The successor additionally closes
|
||||||
|
every concrete FK-like comment field in the pinned schema (labels, projects,
|
||||||
|
milestones, tracked time, assignees, dependency, review and cross-reference),
|
||||||
|
content-history-to-comment, review-team and pull-merger provenance. Target
|
||||||
|
repository and local actor IDs are classified through the exact 2058/972
|
||||||
|
decisions. Migrated external-author IDs are never treated as local users and
|
||||||
|
are represented only by presence counts and author-name byte lengths. A
|
||||||
|
nonzero comment-assignee or review-reviewer team must resolve to an exact team
|
||||||
|
and organization; because no organization/team allowlist exists, its row,
|
||||||
|
team and organization IDs enter a sealed HOLD inventory and add the narrowly
|
||||||
|
scoped `issue-pr-team-mapping-hold` blocker. Orphans, conflicting user/team
|
||||||
|
identities, invalid cross-reference pairs and schema/type drift fail closed.
|
||||||
|
The report is still not a physical-file or sanitized-archive verifier, so
|
||||||
|
those separate blockers remain explicit.
|
||||||
|
The schema-catalog digest is recorded as observed evidence but deliberately
|
||||||
|
remains unpinned as reviewed activation authority. Schema absence/type drift,
|
||||||
|
invalid numeric/OID state or aggregate overflow is a hard error. `apply` fails
|
||||||
|
before creating `/volume1/docker/nodedc-gitea` until all of the following are
|
||||||
|
registered:
|
||||||
|
|
||||||
|
- root-owned Btrfs proof that the named snapshot UUID is read-only;
|
||||||
|
- exact stopped/restart-`no` legacy container image ID, image ref and sole
|
||||||
|
`/volume1/docker/gitea:/data` bind inspect;
|
||||||
|
- a separately verified exact reference/OID manifest digest, fsck,
|
||||||
|
reachability and selected-object reconstruction;
|
||||||
|
- immutable sanitized archives for issues, PRs, comments, releases, labels,
|
||||||
|
projects and repository descriptions, with no legacy row import;
|
||||||
|
- physical attachment inventory and reachable LFS pointer/OID/size/SHA proof;
|
||||||
|
- kept-user mapping before any collaboration recreation; and
|
||||||
|
- package/Actions schema plus physical zero closure and target unit-policy
|
||||||
|
acceptance.
|
||||||
|
|
||||||
|
All 25 direct repository relations have explicit dispositions, including the
|
||||||
|
15 exact zero-and-drop categories. Pull base/head counts remain directional
|
||||||
|
views of the same five PRs. All repository numeric hints are dropped and
|
||||||
|
recomputed. Repo-unit types 1–10 are total: clean types 1–5 and 8 are recreated
|
||||||
|
once per repository; external wiki/tracker are forbidden; Packages (9) and
|
||||||
|
Actions (10) are absent in the target and remain globally disabled. Legacy
|
||||||
|
unit config is never imported. The topics verifier accepts exact JSON `null`
|
||||||
|
as semantic empty state, or a canonical JSON array of sorted unique lowercase
|
||||||
|
names matching `^[a-z0-9][-.a-z0-9]*$` with at most 35 UTF-8 bytes. SQL NULL,
|
||||||
|
whitespace/case variants, quoted `"null"`, objects, numbers, booleans and
|
||||||
|
noncanonical arrays fail closed. The current snapshot must prove exactly 45
|
||||||
|
serialized JSON nulls, zero serialized arrays, zero material topics and zero
|
||||||
|
`repo_topic` rows; target cache is rebuilt from relations.
|
||||||
|
|
||||||
|
The activation hook remains frozen until these blockers are closed and the
|
||||||
|
full clean-database recreation, reflink reconstruction, doctor/fsck,
|
||||||
|
credential-table zeroing, rollback and public acceptance path has its own
|
||||||
|
runtime smoke. The current reviewed source identities are:
|
||||||
|
|
||||||
|
- runner `nodedc-deploy` SHA-256
|
||||||
|
`c766985aa02fe911fa5a873717276f3503d9cecded448e41dc6945030dcbce9e`;
|
||||||
|
- deterministic builder SHA-256
|
||||||
|
`b92076583aeab854015a05deb9d942c8dfcf3ca475fd3d52115d402d6f61acca`;
|
||||||
|
- salvage policy test SHA-256
|
||||||
|
`99b96b6179286a78e7d53ee6a923fc092e3471a8cece382a472449d455ad212c`;
|
||||||
|
- fresh-install policy test SHA-256
|
||||||
|
`20653c640142c25e7d2ef712048acc7889c56e1d519e2d03b5cad4127e48947c`;
|
||||||
|
- confirmed disposition SHA-256
|
||||||
|
`0a066724bcf6e4933133db6cab6cc273393e3c262dd00dda0bbf9ceebd84f78c`;
|
||||||
|
- confirmed closure disposition SHA-256
|
||||||
|
`7ed66d9848268431a703fe24b22c41afbaa7c5ff48949604d6fc448d93e0d243`;
|
||||||
|
- successor v3 descriptor SHA-256
|
||||||
|
`9b98eb1a1640fd5569cf051a621837379b167eff4527313a43a0a851e7cc181a`;
|
||||||
|
- deterministic local review artifact
|
||||||
|
`nodedc-gitea-gitea-incident-salvage-subrelation-closure-20260814-006.tgz`
|
||||||
|
SHA-256
|
||||||
|
`d3e598cf892b1371912dcd9cef64caa991dd23613e48fe979d0f95d493c23d53`.
|
||||||
|
|
||||||
|
Only after that exact runner is separately reviewed, promoted to the
|
||||||
|
root-owned runner path and its installed SHA-256 is re-attested may the exact
|
||||||
|
artifact be staged into the canonical inbox for a canonical `plan` command.
|
||||||
|
Staging is evidence collection only; it grants no authority to run `apply`.
|
||||||
|
The legacy identity is now pinned to the observed ref `gitea/gitea:latest`
|
||||||
|
and immutable image ID
|
||||||
|
`sha256:bf95d9a45ce4fe38b027d051cdc4a4bc531513489fa6244af4074efbb1c376d6`.
|
||||||
|
The mutable tag is provenance only. Every plan re-inspects exact name
|
||||||
|
`/gitea`, stopped state, restart `no`, and the sole RW bind
|
||||||
|
`/volume1/docker/gitea:/data`; drift is a hard stop.
|
||||||
|
|
||||||
|
The successor plan must report these exact remaining blockers:
|
||||||
|
|
||||||
|
- `attachment-physical-verifier-pending`;
|
||||||
|
- `candidate-root-activation-hard-frozen`;
|
||||||
|
- `closure-report-review-pin-pending`;
|
||||||
|
- `collaboration-kept-user-mapping-verifier-pending`;
|
||||||
|
- `forensic-ref-archive-verifier-pending`;
|
||||||
|
- `issue-pr-metadata-sanitized-archive-verifier-pending`;
|
||||||
|
- `lfs-reachable-pointer-physical-verifier-pending`;
|
||||||
|
- `package-action-physical-closure-verifier-pending`;
|
||||||
|
- `reference-manifest-fsck-reachability-verifier-pending`;
|
||||||
|
- `repository-object-reconstruction-verifier-pending`;
|
||||||
|
- `target-unit-policy-acceptance-pending`;
|
||||||
|
- `unsupported-schema-catalog-verifier-pending`.
|
||||||
|
|
||||||
|
Any other blocker or identity drift is also a stop. `apply` remains prohibited
|
||||||
|
and hard-frozen before root creation until a subsequent reviewed runner
|
||||||
|
revision closes every blocker and implements and smoke-tests the complete
|
||||||
|
activation/rollback path. The successor files must be independently reviewed
|
||||||
|
before any runner promotion or inbox staging.
|
||||||
|
|
||||||
|
Run the bounded policy suite with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
python3 infra/deploy-runner/test_gitea_incident_salvage.py -v
|
||||||
|
```
|
||||||
|
|
||||||
|
The frozen local cutoff passes 54 incident-salvage tests and 26 fresh-install
|
||||||
|
tests, plus Python compilation and `git diff --check`.
|
||||||
|
|
||||||
`n8n-private-extension` is a staging-only trust boundary for reviewed offline
|
`n8n-private-extension` is a staging-only trust boundary for reviewed offline
|
||||||
n8n private-node releases. Its artifact may contain exactly one digest-bound
|
n8n private-node releases. Its artifact may contain exactly one digest-bound
|
||||||
`n8n-nodes-ndc` release with `package.tgz`, `release.json` and
|
`n8n-nodes-ndc` release with `package.tgz`, `release.json` and
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
import {
|
||||||
|
cp,
|
||||||
|
lstat,
|
||||||
|
mkdir,
|
||||||
|
mkdtemp,
|
||||||
|
readFile,
|
||||||
|
rm,
|
||||||
|
writeFile,
|
||||||
|
} from "node:fs/promises";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { dirname, join, resolve } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const fixtureRoot = resolve(scriptDir, "fixtures/gitea");
|
||||||
|
const artifactDir = resolve(
|
||||||
|
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|
||||||
|
|| resolve(scriptDir, "../deploy-artifacts"),
|
||||||
|
);
|
||||||
|
const [patchId = "gitea-fresh-install-20260813-001", ...extra] =
|
||||||
|
process.argv.slice(2);
|
||||||
|
|
||||||
|
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
|
||||||
|
throw new Error("usage: build-gitea-fresh-install-artifact.mjs [patch-id]");
|
||||||
|
}
|
||||||
|
|
||||||
|
const composeRelative = "docker-compose.gitea.yml";
|
||||||
|
const descriptorRelative = "deployment/gitea-fresh-install-v1.json";
|
||||||
|
const files = [composeRelative, descriptorRelative];
|
||||||
|
const stage = await mkdtemp(join(tmpdir(), "nodedc-gitea-artifact-"));
|
||||||
|
const payload = join(stage, "payload");
|
||||||
|
const target = join(artifactDir, `nodedc-gitea-${patchId}.tgz`);
|
||||||
|
|
||||||
|
await assertFixtureContract();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mkdir(payload, { recursive: true });
|
||||||
|
for (const relative of files) {
|
||||||
|
const source = resolve(fixtureRoot, relative);
|
||||||
|
const sourceStat = await lstat(source);
|
||||||
|
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
|
||||||
|
throw new Error(`gitea_fixture_type_rejected:${relative}`);
|
||||||
|
}
|
||||||
|
const destination = join(payload, relative);
|
||||||
|
await mkdir(dirname(destination), { recursive: true });
|
||||||
|
await cp(source, destination, { force: true, verbatimSymlinks: true });
|
||||||
|
}
|
||||||
|
await writeFile(
|
||||||
|
join(stage, "manifest.env"),
|
||||||
|
`id=${patchId}\ncomponent=gitea\ntype=app-overlay\n`,
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
await writeFile(join(stage, "files.txt"), `${files.join("\n")}\n`, "utf8");
|
||||||
|
await mkdir(artifactDir, { recursive: true });
|
||||||
|
|
||||||
|
const tar = spawnSync(
|
||||||
|
"python3",
|
||||||
|
["-c", canonicalTarScript(), target, stage],
|
||||||
|
{ encoding: "utf8", maxBuffer: 16 * 1024 * 1024 },
|
||||||
|
);
|
||||||
|
if (tar.status !== 0) {
|
||||||
|
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const digest = sha256(await readFile(target));
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
patchId,
|
||||||
|
artifact: target,
|
||||||
|
sha256: digest,
|
||||||
|
component: "gitea",
|
||||||
|
entries: files,
|
||||||
|
services: ["gitea"],
|
||||||
|
image: "docker.gitea.com/gitea:1.27.1-rootless@sha256:89dc3c214b3992e5bb01e05ad21139d7a8b302d3ea3d8942d3f7e904e92af148",
|
||||||
|
installMode: "fresh-only",
|
||||||
|
database: "fresh-sqlite-only",
|
||||||
|
lfs: "disabled-pending-reviewed-restore-transition",
|
||||||
|
transport: "unix:/run/gitea/gitea.sock",
|
||||||
|
networkMode: "none",
|
||||||
|
minimumComposeVersion: "2.20.1",
|
||||||
|
preserved: ["legacy-gitea-root-unread-and-untouched"],
|
||||||
|
excluded: [
|
||||||
|
"secrets",
|
||||||
|
"runtime-data",
|
||||||
|
"database",
|
||||||
|
"repositories",
|
||||||
|
"users",
|
||||||
|
"tokens",
|
||||||
|
"ssh-keys",
|
||||||
|
"hooks",
|
||||||
|
],
|
||||||
|
}, null, 2));
|
||||||
|
} finally {
|
||||||
|
await rm(stage, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assertFixtureContract() {
|
||||||
|
const composeBytes = await readFile(resolve(fixtureRoot, composeRelative));
|
||||||
|
const compose = composeBytes.toString("utf8");
|
||||||
|
const descriptor = JSON.parse(
|
||||||
|
await readFile(resolve(fixtureRoot, descriptorRelative), "utf8"),
|
||||||
|
);
|
||||||
|
const expectedImage =
|
||||||
|
"docker.gitea.com/gitea:1.27.1-rootless@sha256:89dc3c214b3992e5bb01e05ad21139d7a8b302d3ea3d8942d3f7e904e92af148";
|
||||||
|
const required = [
|
||||||
|
`image: ${expectedImage}`,
|
||||||
|
"platform: linux/amd64",
|
||||||
|
"pull_policy: never",
|
||||||
|
"network_mode: none",
|
||||||
|
'user: "1000:1000"',
|
||||||
|
"stop_grace_period: 30s",
|
||||||
|
"driver: json-file",
|
||||||
|
'max-size: "10m"',
|
||||||
|
'max-file: "3"',
|
||||||
|
"GITEA__server__PROTOCOL: http+unix",
|
||||||
|
"GITEA__server__HTTP_ADDR: /run/gitea/gitea.sock",
|
||||||
|
'GITEA__server__UNIX_SOCKET_PERMISSION: "0666"',
|
||||||
|
"GITEA__server__LOCAL_ROOT_URL: http://unix/",
|
||||||
|
'GITEA__server__DISABLE_SSH: "true"',
|
||||||
|
'GITEA__server__LFS_START_SERVER: "false"',
|
||||||
|
'GITEA__server__LFS_ALLOW_PURE_SSH: "false"',
|
||||||
|
"GITEA__security__SECRET_KEY_URI: file:/run/secrets/gitea_secret_key",
|
||||||
|
"GITEA__security__INTERNAL_TOKEN_URI: file:/run/secrets/gitea_internal_token",
|
||||||
|
"GITEA__security__TWO_FACTOR_AUTH: enforced",
|
||||||
|
'GITEA__security__REVERSE_PROXY_LIMIT: "1"',
|
||||||
|
"GITEA__security__ALLOWED_HOST_LIST: loopback",
|
||||||
|
"GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES: 127.0.0.0/8,::1/128",
|
||||||
|
'GITEA__service__DISABLE_REGISTRATION: "true"',
|
||||||
|
'GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION: "false"',
|
||||||
|
'GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION_API: "false"',
|
||||||
|
'GITEA__service__ENABLE_REVERSE_PROXY_AUTO_REGISTRATION: "false"',
|
||||||
|
'GITEA__service__ENABLE_BASIC_AUTHENTICATION: "false"',
|
||||||
|
'GITEA__admin__DISABLE_REGULAR_ORG_CREATION: "true"',
|
||||||
|
"GITEA__admin__USER_DISABLED_FEATURES: deletion,manage_ssh_keys,manage_gpg_keys,change_username",
|
||||||
|
'GITEA__security__DISABLE_GIT_HOOKS: "true"',
|
||||||
|
'GITEA__security__DISABLE_WEBHOOKS: "true"',
|
||||||
|
'GITEA__repository__DISABLE_MIGRATIONS: "true"',
|
||||||
|
'GITEA__packages__ENABLED: "false"',
|
||||||
|
'GITEA__oauth2__ENABLED: "false"',
|
||||||
|
'GITEA__openid__ENABLE_OPENID_SIGNIN: "false"',
|
||||||
|
'GITEA__cron.update_checker__ENABLED: "false"',
|
||||||
|
"source: /volume1/docker/nodedc-gitea/socket",
|
||||||
|
"target: /run/gitea",
|
||||||
|
"create_host_path: false",
|
||||||
|
"read_only: true",
|
||||||
|
"no-new-privileges:true",
|
||||||
|
];
|
||||||
|
for (const fragment of required) {
|
||||||
|
if (!compose.includes(fragment)) {
|
||||||
|
throw new Error(`gitea_compose_boundary_missing:${fragment}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const forbidden of [
|
||||||
|
"4022",
|
||||||
|
"2222:2222",
|
||||||
|
"0.0.0.0:3000",
|
||||||
|
"ports:",
|
||||||
|
"networks:",
|
||||||
|
"/var/run/docker.sock",
|
||||||
|
"/volume1/docker/gitea",
|
||||||
|
"privileged: true",
|
||||||
|
"pull_policy: always",
|
||||||
|
"__FILE",
|
||||||
|
"GITEA__security__SECRET_KEY:",
|
||||||
|
"GITEA__security__INTERNAL_TOKEN:",
|
||||||
|
"GITEA__server__LFS_JWT_SECRET:",
|
||||||
|
"GITEA__server__LFS_JWT_SECRET_URI",
|
||||||
|
"gitea_lfs_jwt_secret",
|
||||||
|
"lfs-jwt-secret",
|
||||||
|
"GITEA__server__REVERSE_PROXY_LIMIT",
|
||||||
|
"GITEA__server__REVERSE_PROXY_TRUSTED_PROXIES",
|
||||||
|
"GITEA__security__ENABLE_REVERSE_PROXY_AUTHENTICATION",
|
||||||
|
"GITEA__security__ENABLE_REVERSE_PROXY_AUTHENTICATION_API",
|
||||||
|
"GITEA__security__ENABLE_REVERSE_PROXY_AUTO_REGISTRATION",
|
||||||
|
"GITEA__service__DISABLE_REGULAR_ORG_CREATION",
|
||||||
|
"GITEA__service__USER_DISABLED_FEATURES",
|
||||||
|
]) {
|
||||||
|
if (compose.includes(forbidden)) {
|
||||||
|
throw new Error(`gitea_compose_boundary_violation:${forbidden}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
descriptor.schemaVersion !== "nodedc.gitea.fresh-install.v1"
|
||||||
|
|| descriptor.action !== "fresh-install"
|
||||||
|
|| descriptor.component !== "gitea"
|
||||||
|
|| descriptor.compose?.sha256 !== sha256(composeBytes)
|
||||||
|
|| descriptor.runtime?.image !== expectedImage
|
||||||
|
|| descriptor.runtime?.minimumComposeVersion !== "2.20.1"
|
||||||
|
|| descriptor.runtime?.lfs !== "disabled-pending-reviewed-restore-transition"
|
||||||
|
|| descriptor.runtime?.transport !== "unix:/run/gitea/gitea.sock"
|
||||||
|
|| descriptor.runtime?.networkMode !== "none"
|
||||||
|
|| descriptor.runtime?.logging !== "bounded-json-file-10m-x3"
|
||||||
|
|| descriptor.runtime?.stopGracePeriod !== "30s"
|
||||||
|
|| descriptor.trust?.artifactSecrets !== "forbidden"
|
||||||
|
|| descriptor.trust?.legacyRootAccess !== "forbidden"
|
||||||
|
) {
|
||||||
|
throw new Error("gitea_descriptor_contract_mismatch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sha256(bytes) {
|
||||||
|
return createHash("sha256").update(bytes).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
function canonicalTarScript() {
|
||||||
|
return [
|
||||||
|
"import gzip,io,pathlib,sys,tarfile",
|
||||||
|
"root=pathlib.Path(sys.argv[2])",
|
||||||
|
"with open(sys.argv[1],'wb') as out:",
|
||||||
|
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
|
||||||
|
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
|
||||||
|
" for top in ('manifest.env','files.txt','payload'):",
|
||||||
|
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
|
||||||
|
" for x in paths:",
|
||||||
|
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
|
||||||
|
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
|
||||||
|
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
import {
|
||||||
|
cp,
|
||||||
|
link,
|
||||||
|
lstat,
|
||||||
|
mkdir,
|
||||||
|
mkdtemp,
|
||||||
|
readFile,
|
||||||
|
rm,
|
||||||
|
writeFile,
|
||||||
|
} from "node:fs/promises";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { basename, dirname, join, resolve } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const fixtureRoot = resolve(scriptDir, "fixtures/gitea-salvage");
|
||||||
|
const decisionRoot = resolve(
|
||||||
|
scriptDir,
|
||||||
|
"../../../security-incidents/gitea-20260814/confirmed-decisions-v2",
|
||||||
|
);
|
||||||
|
const artifactDir = resolve(
|
||||||
|
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|
||||||
|
|| resolve(scriptDir, "../deploy-artifacts"),
|
||||||
|
);
|
||||||
|
const [patchId = "gitea-incident-salvage-subrelation-closure-20260814-006", ...extra] =
|
||||||
|
process.argv.slice(2);
|
||||||
|
|
||||||
|
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
|
||||||
|
throw new Error(
|
||||||
|
"usage: build-gitea-incident-salvage-artifact.mjs [patch-id]",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const composeRelative = "docker-compose.gitea.yml";
|
||||||
|
const descriptorRelative = "deployment/gitea-incident-salvage-v3.json";
|
||||||
|
const decisionPrefix = "deployment/gitea-incident-salvage";
|
||||||
|
const dispositionRelative = `${decisionPrefix}/confirmed-disposition-v1.json`;
|
||||||
|
const closureDispositionRelative =
|
||||||
|
`${decisionPrefix}/confirmed-closure-disposition-v1.json`;
|
||||||
|
const descriptorSha256 =
|
||||||
|
"9b98eb1a1640fd5569cf051a621837379b167eff4527313a43a0a851e7cc181a";
|
||||||
|
const dispositionSha256 =
|
||||||
|
"0a066724bcf6e4933133db6cab6cc273393e3c262dd00dda0bbf9ceebd84f78c";
|
||||||
|
const closureDispositionSha256 =
|
||||||
|
"7ed66d9848268431a703fe24b22c41afbaa7c5ff48949604d6fc448d93e0d243";
|
||||||
|
const decisionFiles = [
|
||||||
|
["confirmed-decision.json", "dc9528462624158eb44218d37cc7054d551ca2d7ded562592982aa3f34c9fc2a"],
|
||||||
|
["users.decisions.csv", "e3b82f1073a86eea9e567edff062dd1689d21ec0edcf3ed92e844da9351ee8b6"],
|
||||||
|
["repositories.decisions.csv", "76b4bae2ab5cec490330c19bfc5ae9429abf7636705ae028c1c64fd54a6a0493"],
|
||||||
|
];
|
||||||
|
const files = [
|
||||||
|
composeRelative,
|
||||||
|
descriptorRelative,
|
||||||
|
dispositionRelative,
|
||||||
|
closureDispositionRelative,
|
||||||
|
...decisionFiles.map(([name]) => `${decisionPrefix}/${name}`),
|
||||||
|
];
|
||||||
|
const stage = await mkdtemp(join(tmpdir(), "nodedc-gitea-salvage-artifact-"));
|
||||||
|
const payload = join(stage, "payload");
|
||||||
|
const target = join(artifactDir, `nodedc-gitea-${patchId}.tgz`);
|
||||||
|
const targetTemporary = join(
|
||||||
|
artifactDir,
|
||||||
|
`.${basename(target)}.${process.pid}.tmp`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await assertSourceContract();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mkdir(payload, { recursive: true });
|
||||||
|
for (const relative of [
|
||||||
|
composeRelative,
|
||||||
|
descriptorRelative,
|
||||||
|
dispositionRelative,
|
||||||
|
closureDispositionRelative,
|
||||||
|
]) {
|
||||||
|
await copyRegularFile(resolve(fixtureRoot, relative), join(payload, relative), relative);
|
||||||
|
}
|
||||||
|
for (const [name] of decisionFiles) {
|
||||||
|
await copyRegularFile(
|
||||||
|
resolve(decisionRoot, name),
|
||||||
|
join(payload, decisionPrefix, name),
|
||||||
|
`${decisionPrefix}/${name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await writeFile(
|
||||||
|
join(stage, "manifest.env"),
|
||||||
|
`id=${patchId}\ncomponent=gitea\ntype=app-overlay\n`,
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
await writeFile(join(stage, "files.txt"), `${files.join("\n")}\n`, "utf8");
|
||||||
|
await mkdir(artifactDir, { recursive: true });
|
||||||
|
|
||||||
|
const tar = spawnSync(
|
||||||
|
"python3",
|
||||||
|
["-c", canonicalTarScript(), targetTemporary, stage],
|
||||||
|
{ encoding: "utf8", maxBuffer: 16 * 1024 * 1024 },
|
||||||
|
);
|
||||||
|
if (tar.status !== 0) {
|
||||||
|
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await link(targetTemporary, target);
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.code === "EEXIST") {
|
||||||
|
throw new Error("gitea_salvage_artifact_target_already_exists");
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
patchId,
|
||||||
|
artifact: target,
|
||||||
|
sha256: sha256(await readFile(target)),
|
||||||
|
component: "gitea",
|
||||||
|
transition: "clean-state-incident-salvage",
|
||||||
|
entries: files,
|
||||||
|
services: ["gitea"],
|
||||||
|
image: "docker.gitea.com/gitea:1.27.2-rootless@sha256:7de5f49ada687b8c8d2938f547cdb7634839764ba51f297457bae35cee3abd2c",
|
||||||
|
decisions: {
|
||||||
|
users: { active: 2, locked: 8, delete: 962 },
|
||||||
|
repositories: { keep: 45, delete: 2013 },
|
||||||
|
},
|
||||||
|
disposition: {
|
||||||
|
sha256: dispositionSha256,
|
||||||
|
forensicRefs: 105,
|
||||||
|
liveRefs: 93,
|
||||||
|
archiveOnlyRefs: 12,
|
||||||
|
},
|
||||||
|
closureDisposition: {
|
||||||
|
sha256: closureDispositionSha256,
|
||||||
|
state: "plan-report-review-pending",
|
||||||
|
},
|
||||||
|
stateBoundary: "new-database-config-secrets-identities",
|
||||||
|
repositoryBoundary: "exact-45-object-and-validated-ref-material-only",
|
||||||
|
networkMode: "none",
|
||||||
|
transport: "unix:/run/gitea/gitea.sock",
|
||||||
|
stagePolicy: "after-exact-runner-promotion-plan-only",
|
||||||
|
applyPolicy: "hard-frozen-before-root-creation",
|
||||||
|
}, null, 2));
|
||||||
|
} finally {
|
||||||
|
await rm(targetTemporary, { force: true });
|
||||||
|
await rm(stage, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyRegularFile(source, destination, label) {
|
||||||
|
const sourceStat = await lstat(source);
|
||||||
|
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
|
||||||
|
throw new Error(`gitea_salvage_source_type_rejected:${label}`);
|
||||||
|
}
|
||||||
|
await mkdir(dirname(destination), { recursive: true });
|
||||||
|
await cp(source, destination, { force: true, verbatimSymlinks: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function canonicalJson(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map(canonicalJson);
|
||||||
|
}
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.keys(value).sort().map((key) => [key, canonicalJson(value[key])]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assertSourceContract() {
|
||||||
|
for (const [name, expected] of decisionFiles) {
|
||||||
|
const bytes = await readFile(resolve(decisionRoot, name));
|
||||||
|
if (sha256(bytes) !== expected) {
|
||||||
|
throw new Error(`gitea_salvage_decision_digest_mismatch:${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const composeBytes = await readFile(resolve(fixtureRoot, composeRelative));
|
||||||
|
const compose = composeBytes.toString("utf8");
|
||||||
|
const descriptorBytes = await readFile(
|
||||||
|
resolve(fixtureRoot, descriptorRelative),
|
||||||
|
);
|
||||||
|
const descriptor = JSON.parse(descriptorBytes.toString("utf8"));
|
||||||
|
if (sha256(descriptorBytes) !== descriptorSha256) {
|
||||||
|
throw new Error("gitea_salvage_descriptor_digest_mismatch");
|
||||||
|
}
|
||||||
|
const dispositionBytes = await readFile(
|
||||||
|
resolve(fixtureRoot, dispositionRelative),
|
||||||
|
);
|
||||||
|
const disposition = JSON.parse(dispositionBytes.toString("utf8"));
|
||||||
|
if (sha256(dispositionBytes) !== dispositionSha256) {
|
||||||
|
throw new Error("gitea_salvage_disposition_digest_mismatch");
|
||||||
|
}
|
||||||
|
const canonicalDisposition = Buffer.from(
|
||||||
|
`${JSON.stringify(canonicalJson(disposition))}\n`,
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
if (!dispositionBytes.equals(canonicalDisposition)) {
|
||||||
|
throw new Error("gitea_salvage_disposition_not_canonical");
|
||||||
|
}
|
||||||
|
const closureDispositionBytes = await readFile(
|
||||||
|
resolve(fixtureRoot, closureDispositionRelative),
|
||||||
|
);
|
||||||
|
const closureDisposition = JSON.parse(closureDispositionBytes.toString("utf8"));
|
||||||
|
if (sha256(closureDispositionBytes) !== closureDispositionSha256) {
|
||||||
|
throw new Error("gitea_salvage_closure_disposition_digest_mismatch");
|
||||||
|
}
|
||||||
|
const canonicalClosureDisposition = Buffer.from(
|
||||||
|
`${JSON.stringify(canonicalJson(closureDisposition))}\n`,
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
if (!closureDispositionBytes.equals(canonicalClosureDisposition)) {
|
||||||
|
throw new Error("gitea_salvage_closure_disposition_not_canonical");
|
||||||
|
}
|
||||||
|
const expectedImage =
|
||||||
|
"docker.gitea.com/gitea:1.27.2-rootless@sha256:7de5f49ada687b8c8d2938f547cdb7634839764ba51f297457bae35cee3abd2c";
|
||||||
|
for (const required of [
|
||||||
|
`image: ${expectedImage}`,
|
||||||
|
"platform: linux/amd64",
|
||||||
|
"pull_policy: never",
|
||||||
|
"network_mode: none",
|
||||||
|
'user: "1000:1000"',
|
||||||
|
"entrypoint:",
|
||||||
|
"- /usr/local/bin/gitea",
|
||||||
|
"- /etc/gitea/app.ini",
|
||||||
|
"read_only: true",
|
||||||
|
"cap_drop:",
|
||||||
|
"- ALL",
|
||||||
|
"no-new-privileges:true",
|
||||||
|
"source: /volume1/docker/nodedc-gitea/data",
|
||||||
|
"target: /data",
|
||||||
|
"source: /volume1/docker/nodedc-gitea/config",
|
||||||
|
"target: /etc/gitea",
|
||||||
|
"source: /volume1/docker/nodedc-gitea/socket",
|
||||||
|
"target: /run/gitea",
|
||||||
|
"create_host_path: false",
|
||||||
|
]) {
|
||||||
|
if (!compose.includes(required)) {
|
||||||
|
throw new Error(`gitea_salvage_compose_boundary_missing:${required}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const forbidden of [
|
||||||
|
"ports:",
|
||||||
|
"networks:",
|
||||||
|
"/var/run/docker.sock",
|
||||||
|
"/volume1/docker/gitea",
|
||||||
|
"privileged: true",
|
||||||
|
"4022",
|
||||||
|
"TWO_FACTOR_AUTH",
|
||||||
|
"LFS_JWT_SECRET",
|
||||||
|
]) {
|
||||||
|
if (compose.includes(forbidden)) {
|
||||||
|
throw new Error(`gitea_salvage_compose_boundary_violation:${forbidden}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
descriptor.schemaVersion !== "nodedc.gitea.incident-salvage.v3"
|
||||||
|
|| descriptor.action !== "clean-state-salvage"
|
||||||
|
|| descriptor.compose?.sha256 !== sha256(composeBytes)
|
||||||
|
|| descriptor.runtime?.image !== expectedImage
|
||||||
|
|| descriptor.runtime?.networkMode !== "none"
|
||||||
|
|| descriptor.runtime?.database !== "new-sqlite-1.27.2-only"
|
||||||
|
|| descriptor.disposition?.file !== dispositionRelative
|
||||||
|
|| descriptor.disposition?.sha256 !== dispositionSha256
|
||||||
|
|| descriptor.closureDisposition?.file !== closureDispositionRelative
|
||||||
|
|| descriptor.closureDisposition?.sha256 !== closureDispositionSha256
|
||||||
|
|| descriptor.closureDisposition?.predecessorArtifactSha256
|
||||||
|
!== "d6870b5583a2f329eadb4e6cda65fdf4d271532df5ffbf8bfb1403968a434672"
|
||||||
|
|| descriptor.trust?.legacyDatabaseImported !== false
|
||||||
|
|| descriptor.trust?.legacyCredentialsImported !== false
|
||||||
|
|| descriptor.identity?.twoFactorAuthentication
|
||||||
|
!== "not-configured-by-transition"
|
||||||
|
) {
|
||||||
|
throw new Error("gitea_salvage_descriptor_contract_mismatch");
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
disposition.schemaVersion !== "nodedc.gitea.incident-disposition.v1"
|
||||||
|
|| disposition.incidentId !== "gitea-20260814"
|
||||||
|
|| disposition.activation?.allowedOperation !== "canonical-plan-only"
|
||||||
|
|| disposition.activation?.applyFrozen !== true
|
||||||
|
|| disposition.activation?.freezeBoundary
|
||||||
|
!== "before-candidate-root-creation"
|
||||||
|
|| disposition.referencePolicy?.forensicScope?.allDiscoveredRefs !== 105
|
||||||
|
|| disposition.referencePolicy?.exactDecisions?.length !== 105
|
||||||
|
|| disposition.referencePolicy?.liveRestore?.totalRefs !== 93
|
||||||
|
|| disposition.referencePolicy?.archiveOnly?.totalRefs !== 12
|
||||||
|
) {
|
||||||
|
throw new Error("gitea_salvage_disposition_contract_mismatch");
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
closureDisposition.schemaVersion
|
||||||
|
!== "nodedc.gitea.incident-closure-disposition.v1"
|
||||||
|
|| closureDisposition.incidentId !== "gitea-20260814"
|
||||||
|
|| closureDisposition.activation?.allowedOperation !== "canonical-plan-only"
|
||||||
|
|| closureDisposition.activation?.applyFrozen !== true
|
||||||
|
|| closureDisposition.closureReport?.expectedSha256 !== null
|
||||||
|
|| closureDisposition.predecessor?.dispositionSha256 !== dispositionSha256
|
||||||
|
|| closureDisposition.remainingBlockers
|
||||||
|
?.includes("closure-report-review-pin-pending") !== true
|
||||||
|
) {
|
||||||
|
throw new Error("gitea_salvage_closure_disposition_contract_mismatch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sha256(bytes) {
|
||||||
|
return createHash("sha256").update(bytes).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
function canonicalTarScript() {
|
||||||
|
return [
|
||||||
|
"import gzip,io,pathlib,sys,tarfile",
|
||||||
|
"root=pathlib.Path(sys.argv[2])",
|
||||||
|
"with open(sys.argv[1],'xb') as out:",
|
||||||
|
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
|
||||||
|
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
|
||||||
|
" for top in ('manifest.env','files.txt','payload'):",
|
||||||
|
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
|
||||||
|
" for x in paths:",
|
||||||
|
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
|
||||||
|
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
|
||||||
|
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": "nodedc.gitea.incident-salvage.v1",
|
||||||
|
"action": "clean-state-salvage",
|
||||||
|
"component": "gitea",
|
||||||
|
"incidentId": "gitea-20260814",
|
||||||
|
"installRoot": "/volume1/docker/nodedc-gitea",
|
||||||
|
"compose": {
|
||||||
|
"file": "docker-compose.gitea.yml",
|
||||||
|
"project": "nodedc-gitea",
|
||||||
|
"service": "gitea",
|
||||||
|
"sha256": "2f031d5bfff4f42c73cabd8c94487ec3e4f1e1b0a96d3b7eec958904f735908a"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"image": "docker.gitea.com/gitea:1.27.2-rootless@sha256:7de5f49ada687b8c8d2938f547cdb7634839764ba51f297457bae35cee3abd2c",
|
||||||
|
"imageId": "sha256:272085a806e6d182352cdb011c0ebab1d2efc7ec45247de84de5659c7bc5c4c6",
|
||||||
|
"repoDigest": "docker.gitea.com/gitea@sha256:7de5f49ada687b8c8d2938f547cdb7634839764ba51f297457bae35cee3abd2c",
|
||||||
|
"platform": "linux/amd64",
|
||||||
|
"pullPolicy": "never",
|
||||||
|
"entrypoint": "/usr/local/bin/gitea",
|
||||||
|
"runAs": "1000:1000",
|
||||||
|
"transport": "unix:/run/gitea/gitea.sock",
|
||||||
|
"networkMode": "none",
|
||||||
|
"ssh": "disabled-no-published-port",
|
||||||
|
"lfs": "unsupported-hard-stop",
|
||||||
|
"database": "new-sqlite-1.27.2-only",
|
||||||
|
"logging": "bounded-json-file-10m-x3",
|
||||||
|
"stopGracePeriod": "30s",
|
||||||
|
"candidateRestartPolicy": "no",
|
||||||
|
"acceptedRestartPolicy": "unless-stopped"
|
||||||
|
},
|
||||||
|
"decision": {
|
||||||
|
"schema": "nodedc.gitea.incident-decision/v2",
|
||||||
|
"manifestFile": "deployment/gitea-incident-salvage/confirmed-decision.json",
|
||||||
|
"manifestSha256": "dc9528462624158eb44218d37cc7054d551ca2d7ded562592982aa3f34c9fc2a",
|
||||||
|
"usersFile": "deployment/gitea-incident-salvage/users.decisions.csv",
|
||||||
|
"usersSha256": "e3b82f1073a86eea9e567edff062dd1689d21ec0edcf3ed92e844da9351ee8b6",
|
||||||
|
"repositoriesFile": "deployment/gitea-incident-salvage/repositories.decisions.csv",
|
||||||
|
"repositoriesSha256": "76b4bae2ab5cec490330c19bfc5ae9429abf7636705ae028c1c64fd54a6a0493",
|
||||||
|
"users": {
|
||||||
|
"active": 2,
|
||||||
|
"locked": 8,
|
||||||
|
"delete": 962
|
||||||
|
},
|
||||||
|
"repositories": {
|
||||||
|
"keep": 45,
|
||||||
|
"delete": 2013,
|
||||||
|
"dctouchPrivate": 32,
|
||||||
|
"silverPublic": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"root": "/volume1/.nodedc-security-snapshots/docker-gitea-incident-20260814",
|
||||||
|
"uuid": "f5a3fe3a-93ea-bb4d-847f-6221a6bcbc9f",
|
||||||
|
"readOnlyRequired": true,
|
||||||
|
"database": "gitea/gitea/gitea.db",
|
||||||
|
"databaseBytes": 182681600,
|
||||||
|
"databaseSha256": "8db9e74a5641662a808d8252c5d6c9de43fe9efd8687634bfbff2d4361a13052",
|
||||||
|
"repositoriesRoot": "gitea/git/repositories"
|
||||||
|
},
|
||||||
|
"trust": {
|
||||||
|
"legacyRootMounted": false,
|
||||||
|
"legacyDatabaseImported": false,
|
||||||
|
"legacyConfigImported": false,
|
||||||
|
"legacyCredentialsImported": false,
|
||||||
|
"artifactSecrets": "forbidden",
|
||||||
|
"runtimeSecrets": "runner-generated",
|
||||||
|
"repositorySelection": "exact-v2-rows-only",
|
||||||
|
"repositoryCopy": "same-filesystem-per-file-reflink-object-and-validated-ref-material-only",
|
||||||
|
"forbiddenRepositoryMaterial": [
|
||||||
|
"hooks",
|
||||||
|
"config",
|
||||||
|
"alternates",
|
||||||
|
"http-alternates",
|
||||||
|
"shallow",
|
||||||
|
"replace",
|
||||||
|
"grafts",
|
||||||
|
"worktrees",
|
||||||
|
"commondir",
|
||||||
|
"lfs"
|
||||||
|
],
|
||||||
|
"unsupportedDatabaseState": "zero-material-rows-required-before-apply"
|
||||||
|
},
|
||||||
|
"identity": {
|
||||||
|
"preserveNumericUserIds": false,
|
||||||
|
"preserveNumericRepositoryIds": false,
|
||||||
|
"oldToNewIdMapping": "/volume1/docker/nodedc-gitea/audit/identity-map.json",
|
||||||
|
"emails": "new-local-noreply-only",
|
||||||
|
"passwordHashes": "never-imported",
|
||||||
|
"activeBootstrapCredentials": "root-only-runner-state-require-change",
|
||||||
|
"lockedCredentials": "fresh-random-discarded-and-login-prohibited",
|
||||||
|
"twoFactorAuthentication": "not-configured-by-transition"
|
||||||
|
},
|
||||||
|
"externalPrerequisites": {
|
||||||
|
"legacyContainer": "exact-inspect-pin-required-stopped-restart-no",
|
||||||
|
"publicProxy": "reviewed-nginx-uds-bridge-and-dsm-loopback-upstream",
|
||||||
|
"firewall": "legacy-isolation-retained-and-loopback-3000-ready",
|
||||||
|
"candidatePublicationGate": "socket-parent-uid1000-gid1023-mode0700",
|
||||||
|
"publishAction": "runner-chmod-socket-parent-0750-after-internal-acceptance",
|
||||||
|
"mustPassBeforeApply": true
|
||||||
|
},
|
||||||
|
"acceptance": {
|
||||||
|
"users": "exact-10-row-by-row",
|
||||||
|
"repositories": "exact-45-row-by-row-and-visibility",
|
||||||
|
"credentialsSessionsKeysWebhooksActions": "zero",
|
||||||
|
"git": "fsck-all-and-public-smart-http-smoke",
|
||||||
|
"registration": "closed",
|
||||||
|
"ssh": "disabled",
|
||||||
|
"network": "none-no-ports",
|
||||||
|
"publicHttps": "required-before-ledger-success"
|
||||||
|
},
|
||||||
|
"rollback": "stop-remove-candidate-prove-absent-quarantine-new-root-never-start-legacy"
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": "nodedc.gitea.incident-salvage.v2",
|
||||||
|
"action": "clean-state-salvage",
|
||||||
|
"component": "gitea",
|
||||||
|
"incidentId": "gitea-20260814",
|
||||||
|
"installRoot": "/volume1/docker/nodedc-gitea",
|
||||||
|
"compose": {
|
||||||
|
"file": "docker-compose.gitea.yml",
|
||||||
|
"project": "nodedc-gitea",
|
||||||
|
"service": "gitea",
|
||||||
|
"sha256": "2f031d5bfff4f42c73cabd8c94487ec3e4f1e1b0a96d3b7eec958904f735908a"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"image": "docker.gitea.com/gitea:1.27.2-rootless@sha256:7de5f49ada687b8c8d2938f547cdb7634839764ba51f297457bae35cee3abd2c",
|
||||||
|
"imageId": "sha256:272085a806e6d182352cdb011c0ebab1d2efc7ec45247de84de5659c7bc5c4c6",
|
||||||
|
"repoDigest": "docker.gitea.com/gitea@sha256:7de5f49ada687b8c8d2938f547cdb7634839764ba51f297457bae35cee3abd2c",
|
||||||
|
"platform": "linux/amd64",
|
||||||
|
"pullPolicy": "never",
|
||||||
|
"entrypoint": "/usr/local/bin/gitea",
|
||||||
|
"runAs": "1000:1000",
|
||||||
|
"transport": "unix:/run/gitea/gitea.sock",
|
||||||
|
"networkMode": "none",
|
||||||
|
"ssh": "disabled-no-published-port",
|
||||||
|
"lfs": "disabled-pending-reachable-pointer-physical-sha-verifier",
|
||||||
|
"database": "new-sqlite-1.27.2-only",
|
||||||
|
"logging": "bounded-json-file-10m-x3",
|
||||||
|
"stopGracePeriod": "30s",
|
||||||
|
"candidateRestartPolicy": "no",
|
||||||
|
"acceptedRestartPolicy": "unless-stopped"
|
||||||
|
},
|
||||||
|
"decision": {
|
||||||
|
"schema": "nodedc.gitea.incident-decision/v2",
|
||||||
|
"manifestFile": "deployment/gitea-incident-salvage/confirmed-decision.json",
|
||||||
|
"manifestSha256": "dc9528462624158eb44218d37cc7054d551ca2d7ded562592982aa3f34c9fc2a",
|
||||||
|
"usersFile": "deployment/gitea-incident-salvage/users.decisions.csv",
|
||||||
|
"usersSha256": "e3b82f1073a86eea9e567edff062dd1689d21ec0edcf3ed92e844da9351ee8b6",
|
||||||
|
"repositoriesFile": "deployment/gitea-incident-salvage/repositories.decisions.csv",
|
||||||
|
"repositoriesSha256": "76b4bae2ab5cec490330c19bfc5ae9429abf7636705ae028c1c64fd54a6a0493",
|
||||||
|
"users": {
|
||||||
|
"active": 2,
|
||||||
|
"locked": 8,
|
||||||
|
"delete": 962
|
||||||
|
},
|
||||||
|
"repositories": {
|
||||||
|
"keep": 45,
|
||||||
|
"delete": 2013,
|
||||||
|
"dctouchPrivate": 32,
|
||||||
|
"silverPublic": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disposition": {
|
||||||
|
"schema": "nodedc.gitea.incident-disposition.v1",
|
||||||
|
"file": "deployment/gitea-incident-salvage/confirmed-disposition-v1.json",
|
||||||
|
"sha256": "0a066724bcf6e4933133db6cab6cc273393e3c262dd00dda0bbf9ceebd84f78c"
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"root": "/volume1/.nodedc-security-snapshots/docker-gitea-incident-20260814",
|
||||||
|
"uuid": "f5a3fe3a-93ea-bb4d-847f-6221a6bcbc9f",
|
||||||
|
"readOnlyRequired": true,
|
||||||
|
"database": "gitea/gitea/gitea.db",
|
||||||
|
"databaseBytes": 182681600,
|
||||||
|
"databaseSha256": "8db9e74a5641662a808d8252c5d6c9de43fe9efd8687634bfbff2d4361a13052",
|
||||||
|
"repositoriesRoot": "gitea/git/repositories"
|
||||||
|
},
|
||||||
|
"trust": {
|
||||||
|
"legacyRootMounted": false,
|
||||||
|
"legacyDatabaseImported": false,
|
||||||
|
"legacyConfigImported": false,
|
||||||
|
"legacyCredentialsImported": false,
|
||||||
|
"artifactSecrets": "forbidden",
|
||||||
|
"runtimeSecrets": "runner-generated",
|
||||||
|
"repositorySelection": "exact-v2-rows-only",
|
||||||
|
"repositoryCopy": "same-filesystem-per-file-reflink-object-and-validated-ref-material-only",
|
||||||
|
"forbiddenRepositoryMaterial": [
|
||||||
|
"hooks",
|
||||||
|
"config",
|
||||||
|
"alternates",
|
||||||
|
"http-alternates",
|
||||||
|
"shallow",
|
||||||
|
"replace",
|
||||||
|
"grafts",
|
||||||
|
"worktrees",
|
||||||
|
"commondir",
|
||||||
|
"lfs"
|
||||||
|
],
|
||||||
|
"unsupportedDatabaseState": "confirmed-disposition-bound-no-legacy-row-import-verifiers-pending"
|
||||||
|
},
|
||||||
|
"identity": {
|
||||||
|
"preserveNumericUserIds": false,
|
||||||
|
"preserveNumericRepositoryIds": false,
|
||||||
|
"oldToNewIdMapping": "/volume1/docker/nodedc-gitea/audit/identity-map.json",
|
||||||
|
"emails": "new-local-noreply-only",
|
||||||
|
"passwordHashes": "never-imported",
|
||||||
|
"activeBootstrapCredentials": "root-only-runner-state-require-change",
|
||||||
|
"lockedCredentials": "fresh-random-discarded-and-login-prohibited",
|
||||||
|
"twoFactorAuthentication": "not-configured-by-transition"
|
||||||
|
},
|
||||||
|
"externalPrerequisites": {
|
||||||
|
"legacyContainer": "exact-inspect-pin-required-stopped-restart-no",
|
||||||
|
"publicProxy": "reviewed-nginx-uds-bridge-and-dsm-loopback-upstream",
|
||||||
|
"firewall": "legacy-isolation-retained-and-loopback-3000-ready",
|
||||||
|
"candidatePublicationGate": "socket-parent-uid1000-gid1023-mode0700",
|
||||||
|
"publishAction": "runner-chmod-socket-parent-0750-after-internal-acceptance",
|
||||||
|
"mustPassBeforeApply": true
|
||||||
|
},
|
||||||
|
"acceptance": {
|
||||||
|
"users": "exact-10-row-by-row",
|
||||||
|
"repositories": "exact-45-row-by-row-and-visibility",
|
||||||
|
"credentialsSessionsKeysWebhooksActions": "zero",
|
||||||
|
"git": "fsck-all-and-public-smart-http-smoke",
|
||||||
|
"registration": "closed",
|
||||||
|
"ssh": "disabled",
|
||||||
|
"network": "none-no-ports",
|
||||||
|
"publicHttps": "required-before-ledger-success"
|
||||||
|
},
|
||||||
|
"rollback": "stop-remove-candidate-prove-absent-quarantine-new-root-never-start-legacy"
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": "nodedc.gitea.incident-salvage.v3",
|
||||||
|
"action": "clean-state-salvage",
|
||||||
|
"component": "gitea",
|
||||||
|
"incidentId": "gitea-20260814",
|
||||||
|
"installRoot": "/volume1/docker/nodedc-gitea",
|
||||||
|
"compose": {
|
||||||
|
"file": "docker-compose.gitea.yml",
|
||||||
|
"project": "nodedc-gitea",
|
||||||
|
"service": "gitea",
|
||||||
|
"sha256": "2f031d5bfff4f42c73cabd8c94487ec3e4f1e1b0a96d3b7eec958904f735908a"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"image": "docker.gitea.com/gitea:1.27.2-rootless@sha256:7de5f49ada687b8c8d2938f547cdb7634839764ba51f297457bae35cee3abd2c",
|
||||||
|
"imageId": "sha256:272085a806e6d182352cdb011c0ebab1d2efc7ec45247de84de5659c7bc5c4c6",
|
||||||
|
"repoDigest": "docker.gitea.com/gitea@sha256:7de5f49ada687b8c8d2938f547cdb7634839764ba51f297457bae35cee3abd2c",
|
||||||
|
"platform": "linux/amd64",
|
||||||
|
"pullPolicy": "never",
|
||||||
|
"entrypoint": "/usr/local/bin/gitea",
|
||||||
|
"runAs": "1000:1000",
|
||||||
|
"transport": "unix:/run/gitea/gitea.sock",
|
||||||
|
"networkMode": "none",
|
||||||
|
"ssh": "disabled-no-published-port",
|
||||||
|
"lfs": "disabled-pending-reachable-pointer-physical-sha-verifier",
|
||||||
|
"database": "new-sqlite-1.27.2-only",
|
||||||
|
"logging": "bounded-json-file-10m-x3",
|
||||||
|
"stopGracePeriod": "30s",
|
||||||
|
"candidateRestartPolicy": "no",
|
||||||
|
"acceptedRestartPolicy": "unless-stopped"
|
||||||
|
},
|
||||||
|
"decision": {
|
||||||
|
"schema": "nodedc.gitea.incident-decision/v2",
|
||||||
|
"manifestFile": "deployment/gitea-incident-salvage/confirmed-decision.json",
|
||||||
|
"manifestSha256": "dc9528462624158eb44218d37cc7054d551ca2d7ded562592982aa3f34c9fc2a",
|
||||||
|
"usersFile": "deployment/gitea-incident-salvage/users.decisions.csv",
|
||||||
|
"usersSha256": "e3b82f1073a86eea9e567edff062dd1689d21ec0edcf3ed92e844da9351ee8b6",
|
||||||
|
"repositoriesFile": "deployment/gitea-incident-salvage/repositories.decisions.csv",
|
||||||
|
"repositoriesSha256": "76b4bae2ab5cec490330c19bfc5ae9429abf7636705ae028c1c64fd54a6a0493",
|
||||||
|
"users": {
|
||||||
|
"active": 2,
|
||||||
|
"locked": 8,
|
||||||
|
"delete": 962
|
||||||
|
},
|
||||||
|
"repositories": {
|
||||||
|
"keep": 45,
|
||||||
|
"delete": 2013,
|
||||||
|
"dctouchPrivate": 32,
|
||||||
|
"silverPublic": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disposition": {
|
||||||
|
"schema": "nodedc.gitea.incident-disposition.v1",
|
||||||
|
"file": "deployment/gitea-incident-salvage/confirmed-disposition-v1.json",
|
||||||
|
"sha256": "0a066724bcf6e4933133db6cab6cc273393e3c262dd00dda0bbf9ceebd84f78c"
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"root": "/volume1/.nodedc-security-snapshots/docker-gitea-incident-20260814",
|
||||||
|
"uuid": "f5a3fe3a-93ea-bb4d-847f-6221a6bcbc9f",
|
||||||
|
"readOnlyRequired": true,
|
||||||
|
"database": "gitea/gitea/gitea.db",
|
||||||
|
"databaseBytes": 182681600,
|
||||||
|
"databaseSha256": "8db9e74a5641662a808d8252c5d6c9de43fe9efd8687634bfbff2d4361a13052",
|
||||||
|
"repositoriesRoot": "gitea/git/repositories"
|
||||||
|
},
|
||||||
|
"trust": {
|
||||||
|
"legacyRootMounted": false,
|
||||||
|
"legacyDatabaseImported": false,
|
||||||
|
"legacyConfigImported": false,
|
||||||
|
"legacyCredentialsImported": false,
|
||||||
|
"artifactSecrets": "forbidden",
|
||||||
|
"runtimeSecrets": "runner-generated",
|
||||||
|
"repositorySelection": "exact-v2-rows-only",
|
||||||
|
"repositoryCopy": "same-filesystem-per-file-reflink-object-and-validated-ref-material-only",
|
||||||
|
"forbiddenRepositoryMaterial": [
|
||||||
|
"hooks",
|
||||||
|
"config",
|
||||||
|
"alternates",
|
||||||
|
"http-alternates",
|
||||||
|
"shallow",
|
||||||
|
"replace",
|
||||||
|
"grafts",
|
||||||
|
"worktrees",
|
||||||
|
"commondir",
|
||||||
|
"lfs"
|
||||||
|
],
|
||||||
|
"unsupportedDatabaseState": "closure-disposition-bound-plan-report-review-and-verifiers-pending"
|
||||||
|
},
|
||||||
|
"identity": {
|
||||||
|
"preserveNumericUserIds": false,
|
||||||
|
"preserveNumericRepositoryIds": false,
|
||||||
|
"oldToNewIdMapping": "/volume1/docker/nodedc-gitea/audit/identity-map.json",
|
||||||
|
"emails": "new-local-noreply-only",
|
||||||
|
"passwordHashes": "never-imported",
|
||||||
|
"activeBootstrapCredentials": "root-only-runner-state-require-change",
|
||||||
|
"lockedCredentials": "fresh-random-discarded-and-login-prohibited",
|
||||||
|
"twoFactorAuthentication": "not-configured-by-transition"
|
||||||
|
},
|
||||||
|
"externalPrerequisites": {
|
||||||
|
"legacyContainer": "exact-inspect-pin-required-stopped-restart-no",
|
||||||
|
"publicProxy": "reviewed-nginx-uds-bridge-and-dsm-loopback-upstream",
|
||||||
|
"firewall": "legacy-isolation-retained-and-loopback-3000-ready",
|
||||||
|
"candidatePublicationGate": "socket-parent-uid1000-gid1023-mode0700",
|
||||||
|
"publishAction": "runner-chmod-socket-parent-0750-after-internal-acceptance",
|
||||||
|
"mustPassBeforeApply": true
|
||||||
|
},
|
||||||
|
"acceptance": {
|
||||||
|
"users": "exact-10-row-by-row",
|
||||||
|
"repositories": "exact-45-row-by-row-and-visibility",
|
||||||
|
"credentialsSessionsKeysWebhooksActions": "zero",
|
||||||
|
"git": "fsck-all-and-public-smart-http-smoke",
|
||||||
|
"registration": "closed",
|
||||||
|
"ssh": "disabled",
|
||||||
|
"network": "none-no-ports",
|
||||||
|
"publicHttps": "required-before-ledger-success"
|
||||||
|
},
|
||||||
|
"rollback": "stop-remove-candidate-prove-absent-quarantine-new-root-never-start-legacy",
|
||||||
|
"closureDisposition": {
|
||||||
|
"file": "deployment/gitea-incident-salvage/confirmed-closure-disposition-v1.json",
|
||||||
|
"predecessorArtifactSha256": "d6870b5583a2f329eadb4e6cda65fdf4d271532df5ffbf8bfb1403968a434672",
|
||||||
|
"schema": "nodedc.gitea.incident-closure-disposition.v1",
|
||||||
|
"sha256": "7ed66d9848268431a703fe24b22c41afbaa7c5ff48949604d6fc448d93e0d243"
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"activation":{"allowedOperation":"canonical-plan-only","applyFrozen":true,"freezeBoundary":"before-candidate-root-creation"},"authority":{"policyScope":"access-collaboration-issue-pr-attachment-release-label-project-unit-package-action-closure","source":"owner-instruction-in-current-incident-thread","state":"confirmed-policy-evidence-review-pending"},"closureReport":{"expectedBytes":null,"expectedSha256":null,"reviewState":"canonical-plan-output-unreviewed","schema":"nodedc.gitea.salvage-closure-inventory/v1"},"incidentId":"gitea-20260814","policies":{"accessCollaboration":{"accessCache":"DROP_RESET_RECOMPUTE","actorEvidence":"old-user-id-mode-and-kept-deleted-class-only","collaboration":"RECREATE_ONLY_KEPT_ACTORS_AFTER_REVIEWED_OLD_TO_NEW_ID_MAP","deletedActors":962,"keptActors":10,"legacyRowsImported":false,"sensitiveUserPayloadExported":false},"attachments":{"databaseManifest":"ID_UUID_RELATIONS_DECLARED_SIZE_ONLY","filenameOrContentExported":false,"legacyRowsImported":false,"physicalDisposition":"VERIFY_BYTES_THEN_SANITIZED_IMMUTABLE_ARCHIVE_ONLY","physicalPresenceClaimed":false},"issuesPullRequestsMetadata":{"archive":"SANITIZED_IMMUTABLE_ARCHIVE_ONLY","legacyRowsImported":false,"payloadExportedInPlan":false,"planEvidence":"PER_REPOSITORY_COUNTS_ACTOR_CLASSES_TEXT_BYTE_LENGTHS_ONLY","subrelationClosure":{"commentHistoryMerger":"SCHEMA_BOUND_EXACT_RELATION_COUNTS_AND_CLASSES","externalAuthors":"PRESENCE_AND_NAME_BYTE_LENGTHS_ONLY_NO_LOCAL_USER_MAPPING","legacyRowsImported":false,"teamRelations":"EXACT_ROW_TEAM_ORG_IDS_SEALED_HOLD_AND_BLOCK_IF_PRESENT"},"tables":["comment","issue","issue_assignees","issue_content_history","issue_dependency","issue_label","issue_user","issue_watch","notification","project","project_board","project_issue","pull_auto_merge","pull_request","reaction","review","review_state","stopwatch","tracked_time"]},"packagesActions":{"actionsTarget":"DISABLED","legacyPayloadSecretTokenLogImported":false,"packageTarget":"DISABLED","physicalDisposition":"DROP_ONLY_AFTER_SCHEMA_RELATION_AND_PHYSICAL_CLOSURE_VERIFIER","planEvidence":"RELATIONSHIP_COUNTS_AND_SAFE_DECLARED_SIZES_ONLY"},"releasesLabelsProjects":{"archive":"SANITIZED_IMMUTABLE_ARCHIVE_ONLY","legacyRowsImported":false,"payloadExportedInPlan":false},"repositoryState":{"cachedAccessCountersStarsWatches":"DROP_RESET_RECOMPUTE","legacyHooksWebhooksKeysTokensSessionsCredentialsSecrets":"IMPORT_ZERO","topics":"VERIFIED_SEMANTIC_EMPTY_REBUILD_ZERO"},"units":{"actionsType10":"DISABLED_ZERO_TARGET_ROWS","disabledTypes":[6,7,9,10],"enabledCleanTypes":[1,2,3,4,5,8],"legacyConfigImported":false,"legacyRowsImported":false,"packagesType9":"DISABLED_ZERO_TARGET_ROWS"}},"predecessor":{"artifactSha256":"d6870b5583a2f329eadb4e6cda65fdf4d271532df5ffbf8bfb1403968a434672","dispositionFile":"deployment/gitea-incident-salvage/confirmed-disposition-v1.json","dispositionSha256":"0a066724bcf6e4933133db6cab6cc273393e3c262dd00dda0bbf9ceebd84f78c"},"remainingBlockers":["attachment-physical-verifier-pending","candidate-root-activation-hard-frozen","closure-report-review-pin-pending","collaboration-kept-user-mapping-verifier-pending","forensic-ref-archive-verifier-pending","issue-pr-metadata-sanitized-archive-verifier-pending","lfs-reachable-pointer-physical-verifier-pending","package-action-physical-closure-verifier-pending","reference-manifest-fsck-reachability-verifier-pending","repository-object-reconstruction-verifier-pending","target-unit-policy-acceptance-pending","unsupported-schema-catalog-verifier-pending"],"schemaVersion":"nodedc.gitea.incident-closure-disposition.v1","scope":{"deletedRepositories":2013,"deletedUsers":962,"keptRepositories":45,"keptUsers":10},"sourceEvidence":{"databaseSha256":"8db9e74a5641662a808d8252c5d6c9de43fe9efd8687634bfbff2d4361a13052","identityDecisionManifestSha256":"dc9528462624158eb44218d37cc7054d551ca2d7ded562592982aa3f34c9fc2a","referenceManifestSha256":"9cddaf0e4d4cf22dd264a6ae589ccc50d29e07f85c55e9d34b14627cecb8a311","semanticTopicsSha256":"df6e3612186234bfcf3c172ef4e0fff933baaa691a510f9780ebf9e21c8d4d05","snapshotUuid":"f5a3fe3a-93ea-bb4d-847f-6221a6bcbc9f","unsupportedRepositoryReportSha256":"4b2cecf88c62fc5c4a43419885e88a01c9f9aac03133afb19dae0a7caef106ac","unsupportedSchemaCatalogSha256":"b5e3b6776926c4f1627fafd882362ed0ef986bfc86fc6ac6507a43976531b6db"}}
|
||||||
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,62 @@
|
|||||||
|
name: nodedc-gitea
|
||||||
|
|
||||||
|
services:
|
||||||
|
gitea:
|
||||||
|
image: docker.gitea.com/gitea:1.27.2-rootless@sha256:7de5f49ada687b8c8d2938f547cdb7634839764ba51f297457bae35cee3abd2c
|
||||||
|
platform: linux/amd64
|
||||||
|
pull_policy: never
|
||||||
|
network_mode: none
|
||||||
|
user: "1000:1000"
|
||||||
|
entrypoint:
|
||||||
|
- /usr/local/bin/gitea
|
||||||
|
command:
|
||||||
|
- web
|
||||||
|
- --config
|
||||||
|
- /etc/gitea/app.ini
|
||||||
|
restart: "no"
|
||||||
|
stop_grace_period: 30s
|
||||||
|
init: true
|
||||||
|
read_only: true
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
pids_limit: 512
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
environment:
|
||||||
|
USER: git
|
||||||
|
HOME: /data/gitea
|
||||||
|
GITEA_WORK_DIR: /data/gitea
|
||||||
|
GITEA_CUSTOM: /data/gitea
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: /volume1/docker/nodedc-gitea/data
|
||||||
|
target: /data
|
||||||
|
bind:
|
||||||
|
create_host_path: false
|
||||||
|
- type: bind
|
||||||
|
source: /volume1/docker/nodedc-gitea/config
|
||||||
|
target: /etc/gitea
|
||||||
|
read_only: true
|
||||||
|
bind:
|
||||||
|
create_host_path: false
|
||||||
|
- type: bind
|
||||||
|
source: /volume1/docker/nodedc-gitea/socket
|
||||||
|
target: /run/gitea
|
||||||
|
bind:
|
||||||
|
create_host_path: false
|
||||||
|
secrets:
|
||||||
|
- gitea_secret_key
|
||||||
|
- gitea_internal_token
|
||||||
|
tmpfs:
|
||||||
|
- /tmp:rw,noexec,nosuid,nodev,size=256m
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
gitea_secret_key:
|
||||||
|
file: /volume1/docker/nodedc-gitea/secrets/secret-key
|
||||||
|
gitea_internal_token:
|
||||||
|
file: /volume1/docker/nodedc-gitea/secrets/internal-token
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": "nodedc.gitea.fresh-install.v1",
|
||||||
|
"action": "fresh-install",
|
||||||
|
"component": "gitea",
|
||||||
|
"installRoot": "/volume1/docker/nodedc-gitea",
|
||||||
|
"compose": {
|
||||||
|
"file": "docker-compose.gitea.yml",
|
||||||
|
"project": "nodedc-gitea",
|
||||||
|
"service": "gitea",
|
||||||
|
"sha256": "25868a40996c405543b4627d06499b68f43556e607d839e969a400b0bc0ddadb"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"image": "docker.gitea.com/gitea:1.27.1-rootless@sha256:89dc3c214b3992e5bb01e05ad21139d7a8b302d3ea3d8942d3f7e904e92af148",
|
||||||
|
"platform": "linux/amd64",
|
||||||
|
"pullPolicy": "never",
|
||||||
|
"minimumComposeVersion": "2.20.1",
|
||||||
|
"transport": "unix:/run/gitea/gitea.sock",
|
||||||
|
"socketBind": "/volume1/docker/nodedc-gitea/socket:/run/gitea",
|
||||||
|
"ssh": "disabled-no-published-port",
|
||||||
|
"database": "fresh-sqlite-only",
|
||||||
|
"lfs": "disabled-pending-reviewed-restore-transition",
|
||||||
|
"networkMode": "none",
|
||||||
|
"logging": "bounded-json-file-10m-x3",
|
||||||
|
"stopGracePeriod": "30s"
|
||||||
|
},
|
||||||
|
"trust": {
|
||||||
|
"artifactSecrets": "forbidden",
|
||||||
|
"runtimeSecrets": "runner-managed-file-mounts",
|
||||||
|
"legacyRootAccess": "forbidden",
|
||||||
|
"legacyDatabaseImport": "forbidden",
|
||||||
|
"legacyRepositoryImport": "forbidden"
|
||||||
|
},
|
||||||
|
"reverseProxyPrerequisite": {
|
||||||
|
"managedOutsideArtifact": true,
|
||||||
|
"requiredDsmUpstream": "127.0.0.1:3000",
|
||||||
|
"requiredNginxBridge": "/usr/local/etc/nginx/conf.d/http.nodedc-gitea-uds.conf",
|
||||||
|
"requiredNginxBridgeSha256": "164f37a12a4f91e656cf20bd5b109978d16d723bdfde236653722aaf820780c9",
|
||||||
|
"requiredUnixUpstream": "/volume1/docker/nodedc-gitea/socket/gitea.sock",
|
||||||
|
"mustBeCompletedBeforeApply": true
|
||||||
|
},
|
||||||
|
"rollback": "stop-candidate-preserve-fresh-runtime-state-and-restore-source"
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
name: nodedc-gitea
|
||||||
|
|
||||||
|
services:
|
||||||
|
gitea:
|
||||||
|
image: docker.gitea.com/gitea:1.27.1-rootless@sha256:89dc3c214b3992e5bb01e05ad21139d7a8b302d3ea3d8942d3f7e904e92af148
|
||||||
|
platform: linux/amd64
|
||||||
|
pull_policy: never
|
||||||
|
network_mode: none
|
||||||
|
user: "1000:1000"
|
||||||
|
restart: unless-stopped
|
||||||
|
stop_grace_period: 30s
|
||||||
|
init: true
|
||||||
|
read_only: true
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
pids_limit: 512
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
environment:
|
||||||
|
GITEA_WORK_DIR: /var/lib/gitea
|
||||||
|
GITEA__database__DB_TYPE: sqlite3
|
||||||
|
GITEA__database__PATH: /var/lib/gitea/data/gitea.db
|
||||||
|
GITEA__server__DOMAIN: git.dcserve.ru
|
||||||
|
GITEA__server__ROOT_URL: https://git.dcserve.ru/
|
||||||
|
GITEA__server__PROTOCOL: http+unix
|
||||||
|
GITEA__server__HTTP_ADDR: /run/gitea/gitea.sock
|
||||||
|
GITEA__server__UNIX_SOCKET_PERMISSION: "0666"
|
||||||
|
GITEA__server__LOCAL_ROOT_URL: http://unix/
|
||||||
|
GITEA__server__DISABLE_SSH: "true"
|
||||||
|
GITEA__server__START_SSH_SERVER: "false"
|
||||||
|
GITEA__server__SSH_CREATE_AUTHORIZED_KEYS_FILE: "false"
|
||||||
|
GITEA__server__LFS_START_SERVER: "false"
|
||||||
|
GITEA__server__LFS_ALLOW_PURE_SSH: "false"
|
||||||
|
GITEA__server__OFFLINE_MODE: "true"
|
||||||
|
GITEA__server__LANDING_PAGE: login
|
||||||
|
GITEA__security__INSTALL_LOCK: "true"
|
||||||
|
GITEA__security__SECRET_KEY_URI: file:/run/secrets/gitea_secret_key
|
||||||
|
GITEA__security__INTERNAL_TOKEN_URI: file:/run/secrets/gitea_internal_token
|
||||||
|
GITEA__security__REVERSE_PROXY_LIMIT: "1"
|
||||||
|
GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES: 127.0.0.0/8,::1/128
|
||||||
|
GITEA__security__DISABLE_GIT_HOOKS: "true"
|
||||||
|
GITEA__security__DISABLE_WEBHOOKS: "true"
|
||||||
|
GITEA__security__IMPORT_LOCAL_PATHS: "false"
|
||||||
|
GITEA__security__ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET: "true"
|
||||||
|
GITEA__security__PASSWORD_HASH_ALGO: argon2
|
||||||
|
GITEA__security__MIN_PASSWORD_LENGTH: "16"
|
||||||
|
GITEA__security__PASSWORD_COMPLEXITY: lower,upper,digit,spec
|
||||||
|
GITEA__security__TWO_FACTOR_AUTH: enforced
|
||||||
|
GITEA__security__DISABLE_QUERY_AUTH_TOKEN: "true"
|
||||||
|
GITEA__security__ALLOWED_HOST_LIST: loopback
|
||||||
|
GITEA__service__DISABLE_REGISTRATION: "true"
|
||||||
|
GITEA__service__REQUIRE_SIGNIN_VIEW: "true"
|
||||||
|
GITEA__service__SHOW_REGISTRATION_BUTTON: "false"
|
||||||
|
GITEA__service__DEFAULT_KEEP_EMAIL_PRIVATE: "true"
|
||||||
|
GITEA__service__DEFAULT_ALLOW_CREATE_ORGANIZATION: "false"
|
||||||
|
GITEA__service__DEFAULT_USER_IS_RESTRICTED: "true"
|
||||||
|
GITEA__service__DEFAULT_USER_VISIBILITY: private
|
||||||
|
GITEA__service__ALLOWED_USER_VISIBILITY_MODES: private
|
||||||
|
GITEA__service__DEFAULT_ORG_VISIBILITY: private
|
||||||
|
GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION: "false"
|
||||||
|
GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION_API: "false"
|
||||||
|
GITEA__service__ENABLE_REVERSE_PROXY_AUTO_REGISTRATION: "false"
|
||||||
|
GITEA__service__ENABLE_NOTIFY_MAIL: "false"
|
||||||
|
GITEA__service__ENABLE_BASIC_AUTHENTICATION: "false"
|
||||||
|
GITEA__admin__DISABLE_REGULAR_ORG_CREATION: "true"
|
||||||
|
GITEA__admin__USER_DISABLED_FEATURES: deletion,manage_ssh_keys,manage_gpg_keys,change_username
|
||||||
|
GITEA__repository__FORCE_PRIVATE: "true"
|
||||||
|
GITEA__repository__DEFAULT_PRIVATE: private
|
||||||
|
GITEA__repository__USER_MAX_CREATION_LIMIT: "0"
|
||||||
|
GITEA__repository__ORG_MAX_CREATION_LIMIT: "0"
|
||||||
|
GITEA__repository__ENABLE_PUSH_CREATE_USER: "false"
|
||||||
|
GITEA__repository__ENABLE_PUSH_CREATE_ORG: "false"
|
||||||
|
GITEA__repository__DISABLE_MIGRATIONS: "true"
|
||||||
|
GITEA__repository__ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES: "false"
|
||||||
|
GITEA__repository__ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES: "false"
|
||||||
|
GITEA__repository__DISABLE_HTTP_GIT: "false"
|
||||||
|
GITEA__repository.upload__ENABLED: "false"
|
||||||
|
GITEA__attachment__ENABLED: "false"
|
||||||
|
GITEA__actions__ENABLED: "false"
|
||||||
|
GITEA__packages__ENABLED: "false"
|
||||||
|
GITEA__oauth2__ENABLED: "false"
|
||||||
|
GITEA__oauth2_client__ENABLE_AUTO_REGISTRATION: "false"
|
||||||
|
GITEA__openid__ENABLE_OPENID_SIGNIN: "false"
|
||||||
|
GITEA__openid__ENABLE_OPENID_SIGNUP: "false"
|
||||||
|
GITEA__federation__ENABLED: "false"
|
||||||
|
GITEA__mailer__ENABLED: "false"
|
||||||
|
GITEA__session__COOKIE_SECURE: "true"
|
||||||
|
GITEA__session__SAME_SITE: strict
|
||||||
|
GITEA__api__ENABLE_SWAGGER: "false"
|
||||||
|
GITEA__migrations__ALLOW_LOCALNETWORKS: "false"
|
||||||
|
GITEA__migrations__SKIP_TLS_VERIFY: "false"
|
||||||
|
GITEA__cors__ENABLED: "false"
|
||||||
|
GITEA__metrics__ENABLED: "false"
|
||||||
|
GITEA__cron.update_checker__ENABLED: "false"
|
||||||
|
GITEA__log__MODE: console
|
||||||
|
GITEA__log__LEVEL: Info
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: /volume1/docker/nodedc-gitea/data
|
||||||
|
target: /var/lib/gitea
|
||||||
|
bind:
|
||||||
|
create_host_path: false
|
||||||
|
- type: bind
|
||||||
|
source: /volume1/docker/nodedc-gitea/config
|
||||||
|
target: /etc/gitea
|
||||||
|
bind:
|
||||||
|
create_host_path: false
|
||||||
|
- type: bind
|
||||||
|
source: /volume1/docker/nodedc-gitea/socket
|
||||||
|
target: /run/gitea
|
||||||
|
bind:
|
||||||
|
create_host_path: false
|
||||||
|
secrets:
|
||||||
|
- gitea_secret_key
|
||||||
|
- gitea_internal_token
|
||||||
|
tmpfs:
|
||||||
|
- /tmp:rw,noexec,nosuid,nodev,size=256m
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
gitea_secret_key:
|
||||||
|
file: /volume1/docker/nodedc-gitea/secrets/secret-key
|
||||||
|
gitea_internal_token:
|
||||||
|
file: /volume1/docker/nodedc-gitea/secrets/internal-token
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user