10 KiB
SPEC-001 — Git connection bootstrap (remote origin → Gitea)
| Field | Value |
|---|---|
| Status | Ready for implementation |
| Created | 2026-10-13 |
| Author | OpenArchitect |
| Target milestone | Prep (first slice) — see PROJECT_PLAN.md §11 |
| Governing doctrine | §12 / §12.2 · D-010 (secrets never committed) · D-036 (layout) |
| Related spec | (none) |
1. Goal
Make /root/myday a git repository whose main branch is connected to and verified against
the Gitea remote NextMyDay, with a repeatable, secret-safe push/pull path.
Definition of done: a clean git push -u origin main succeeds and the commit is visible in
Gitea, with no secret present in .git/config, in the commit, or in git remote -v.
1.1 Non-goals (explicitly deferred)
These are later Prep slices and must NOT be built here:
AGENT.md,README.md,config.example.yamldocs/restructuring,docs/specs/index- CI (build + test on push)
- Feature-branch workflow automation
- The Go project skeleton (
cmd/myday/,internal/core/,internal/modules/)
2. Environment facts (discovery, verified 2026-10-13)
| Fact | Evidence |
|---|---|
/root/myday is not a git repo |
no .git/; directory holds only PROJECT_PLAN.md |
| Remote host is reachable and is Gitea 1.22.0 | GET /api/v1/version → 9.0.2+gitea-1.22.0 |
| Remote repo is empty (no commits) | confirmed by operator |
| No git identity configured | /root/.gitconfig does not exist |
No .opencode/ directory exists in the project yet |
ls /root/myday |
3. Architectural decisions introduced by this spec
| ID | Decision | Rationale |
|---|---|---|
| A-01 | Canonical remote is https://git.lohmar.co.uk/cclohmar/NextMyDay.git. The truncated GIT_URL originally supplied (…/cclohmar/) is discarded — an org root is not a valid remote. |
Single source of truth for the remote; prevents a silent misconfiguration. |
| A-02 | The token lives in a credential store outside the repo (~/.git-credentials, mode 600). The remote URL is clean. |
The token must never enter .git/config, git remote -v, shell history, commit objects, or error output. Embedding it in the URL (rejected) leaks through all five. |
| A-03 | AUTO_STASH="true" is realised as git-native config: pull.rebase=true + rebase.autoStash=true. No custom script. |
rebase.autoStash is the platform feature for this requirement. Prefer native over bespoke. |
| A-04 | Two-tier configuration. Git-native settings (identity, remote, branch, pull behaviour) → .git/config. Agent-workflow variables (COMMIT_PREFIX, AUTO_STASH, …) → .opencode/git.env (gitignored) with a committed .opencode/git.env.example. |
Git is authoritative for what git owns; automation knobs are not git's business. Keeps git-native state discoverable via git config --list. |
| A-05 | user.name / user.email are set repo-local only, never --global. |
A sandbox-hosted agent must not mutate machine-wide identity. |
Open decision for the plan (raise as O-12): A-04 introduces
.opencode/git.envas a convention.PROJECT_PLAN.mddoes not yet define where agent-workflow variables live. This spec assumes.opencode/. Confirm before M0 so the convention is documented once.
4. Configuration contract
The canonical variable contract. Committed column states whether the value may ever be committed.
| Variable | Value | Materialises as | Committed? |
|---|---|---|---|
GIT_URL |
https://git.lohmar.co.uk/cclohmar/NextMyDay.git |
remote.origin.url (clean, no token) |
✅ (URL only) |
GIT_TOKEN |
(secret) | ~/.git-credentials via credential.helper=store |
❌ never |
GIT_USER_NAME |
Claus Lohmar ⚠️ assumed — confirm |
.git/config user.name |
✅ |
GIT_USER_EMAIL |
claus.lohmar@lohmar.co.uk |
.git/config user.email |
✅ |
REMOTE_ORIGIN |
origin |
remote.<name> |
✅ |
DEFAULT_BRANCH |
main |
HEAD / init.defaultBranch |
✅ |
COMMIT_PREFIX |
chore: |
commit-message convention (Conventional Commits, §12.2) | ✅ |
AUTO_STASH |
true |
pull.rebase=true + rebase.autoStash=true |
✅ |
5. Structural changes
5.1 Files to CREATE — inside the repo (committed)
| Path | Purpose |
|---|---|
.gitignore |
D-010 gate. Must exist before the first git add. Excludes config.yaml, credentials, DB files, build output. |
.opencode/git.env.example |
Documents the agent-workflow variable contract with placeholder secrets. |
docs/specs/spec-001-git-bootstrap.md |
This spec (already written). |
.gitignore — initial content (minimal, expand later):
# --- secrets: never committed (D-010) ---
config.yaml
.env
.env.*
!.env.example
.opencode/git.env
*.token
*.pem
*.key
# --- credential stores ---
.git-credentials
# --- local runtime state ---
*.db
*.db-wal
*.db-shm
data/
# --- Go build output ---
/bin/
myday
*.exe
# --- editor / OS ---
.DS_Store
.idea/
.vscode/
*.swp
.opencode/git.env.example — content:
# Agent git-workflow contract. Copy to .opencode/git.env (gitignored) and fill secrets.
GIT_URL=https://git.lohmar.co.uk/cclohmar/NextMyDay.git
GIT_TOKEN= # NEVER commit a real value
GIT_USER_NAME=Claus Lohmar
GIT_USER_EMAIL=claus.lohmar@lohmar.co.uk
REMOTE_ORIGIN=origin
DEFAULT_BRANCH=main
COMMIT_PREFIX=chore:
AUTO_STASH=true
5.2 Files to CREATE — outside the repo (never committed)
| Path | Mode | Purpose |
|---|---|---|
/root/.git-credentials |
600 |
Holds https://<user>:<token>@git.lohmar.co.uk. |
/root/myday/.opencode/git.env |
600 |
Local copy of the contract with the real token. |
5.3 Files MODIFIED via git config (not hand-edited)
.git/config gains: user.name, user.email, push.default, pull.rebase,
rebase.autoStash, remote.origin.*, branch.main.*.
/root/.gitconfig gains only credential.helper=store (the sole global write, justified).
5.4 Untouched
PROJECT_PLAN.md — not modified by this spec. (Any plan change, e.g. logging O-12, is a
separate docs: commit.)
6. Developer implementation spec
🏗️ DEVELOPER IMPLEMENTATION SPEC
Target Module: /root/myday (repository root)
Pattern: Bootstrap / idempotent provisioning script
Structural Changes:
- Create
.gitignorebefore any staging (D-010 gate) - Create
.opencode/git.env.example(committed) and.opencode/git.env(600, gitignored) - Create
/root/.git-credentials(600, outside repo) - Initialise repo, set repo-local identity, add clean remote, first commit, push
-u
Contract/API: the variable table in §4. Instructions for the builder:
- Run steps 6.1 → 6.6 in order. Stop and report on any failed assertion.
- Do not set
user.name/user.emailglobally. Do not put the token in the remote URL. - Do not force-push. Do not touch
PROJECT_PLAN.md.
6.1 Create the guard files FIRST
cd /root/myday
mkdir -p docs/specs .opencode
# write .gitignore and .opencode/git.env.example exactly as specified in §5.1
chmod 600 .opencode/git.env # after writing the real values
6.2 Initialise the repository
cd /root/myday
git init -b main # git >= 2.28
# fallback if `-b` is unsupported:
# git init && git symbolic-ref HEAD refs/heads/main
git config --local user.name "Claus Lohmar"
git config --local user.email "claus.lohmar@lohmar.co.uk"
git config --local push.default simple
git config --local pull.rebase true
git config --local rebase.autoStash true # A-03: this is AUTO_STASH="true"
6.3 Credential store (token never touches the repo)
umask 077
printf 'https://%s:%s@git.lohmar.co.uk\n' "cclohmar" "$GIT_TOKEN" > /root/.git-credentials
chmod 600 /root/.git-credentials
git config --global credential.helper store
6.4 Remote (clean URL)
cd /root/myday
git remote add origin https://git.lohmar.co.uk/cclohmar/NextMyDay.git
git remote -v
# ASSERT: output contains no "@" character
6.5 First commit — asserted secret-free
cd /root/myday
git add -A
git status --porcelain
# ASSERT: no config.yaml, no .env, no .opencode/git.env, no *.db, no .git-credentials
git check-ignore -v config.yaml .opencode/git.env # ASSERT: both are ignored
git commit -m "chore: bootstrap MyDay repository"
6.6 Push and verify
cd /root/myday
git push -u origin main
git ls-remote --heads origin main # ASSERT: main present on the remote
git branch -vv # ASSERT: main tracks origin/main
git log --oneline -1
Acceptance criteria
| # | Criterion |
|---|---|
| AC-1 | git remote get-url origin → https://git.lohmar.co.uk/cclohmar/NextMyDay.git (no credentials) |
| AC-2 | git push -u origin main succeeded; main visible in Gitea |
| AC-3 | git log --oneline -1 → subject starting chore: (honours COMMIT_PREFIX) |
| AC-4 | git ls-files contains .gitignore, .opencode/git.env.example, docs/specs/spec-001-git-bootstrap.md, PROJECT_PLAN.md |
| AC-5 | git ls-files contains no config.yaml, .env, .opencode/git.env, *.db |
| AC-6 | git config --local pull.rebase → true; rebase.autoStash → true |
| AC-7 | stat -c '%a' /root/.git-credentials → 600 |
Rollback
cd /root/myday
git remote remove origin
rm -rf /root/myday/.git
rm -f /root/.git-credentials /root/myday/.opencode/git.env
git config --global --unset credential.helper
7. Security notes
- The supplied token was transmitted in plaintext and is therefore considered
compromised. Rotate it in Gitea (
/user/settings/applications) once the connection is verified, then update/root/.git-credentials. Prefer a scoped token (repo write only). .git-credentialsstores the token in plaintext on disk. Acceptable here (single-tenant, root-owned,600). A future hardening step is a keyring/secret-store helper — out of scope.- The
600mode is the only protection; verify it (AC-7). - Consider replacing token-over-HTTPS with an SSH deploy key before CI writes to this repo.
8. Follow-ups (not in this spec)
- Log A-04 / O-12 (where agent-workflow variables live) into
PROJECT_PLAN.md— separatedocs:commit. - Confirm
GIT_USER_NAME(currently assumedClaus Lohmar). - Remaining Prep artefacts:
AGENT.md,README.md,config.example.yaml,docs/layout. - Rotate the exposed token (§7).