# 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.yaml` - `docs/` 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.env` as a > convention. `PROJECT_PLAN.md` does 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.` | ✅ | | `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):** ```gitignore # --- 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:** ```dotenv # 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://:@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 `.gitignore` **before 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:** 1. Run steps 6.1 → 6.6 in order. **Stop and report** on any failed assertion. 2. Do **not** set `user.name`/`user.email` globally. Do **not** put the token in the remote URL. 3. Do **not** force-push. Do **not** touch `PROJECT_PLAN.md`. --- #### 6.1 Create the guard files FIRST ```bash 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 ```bash 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) ```bash 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) ```bash 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 ```bash 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 ```bash 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** ```bash 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-credentials` stores 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 `600` mode 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) 1. Log **A-04 / O-12** (where agent-workflow variables live) into `PROJECT_PLAN.md` — separate `docs:` commit. 2. Confirm `GIT_USER_NAME` (currently assumed `Claus Lohmar`). 3. Remaining Prep artefacts: `AGENT.md`, `README.md`, `config.example.yaml`, `docs/` layout. 4. Rotate the exposed token (§7).