279 lines
10 KiB
Markdown
279 lines
10 KiB
Markdown
# 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.<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):**
|
|
|
|
```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://<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 `.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).
|