diff --git a/.opencode/command/do-pr.md b/.opencode/command/do-pr.md index 9412b4d..e82f8d0 100644 --- a/.opencode/command/do-pr.md +++ b/.opencode/command/do-pr.md @@ -3,17 +3,40 @@ description: Develop the changes for a new pull request agent: build --- -If the user has NOT previously ran the /start-pr command, exit early and ask them to run that command. +IMPORTANT: This command must run the full non-interactive flow for creating a PR. That means it MUST run the test suite(s), commit any changes, push the branch, create the GitHub pull request, update `CHANGELOG.md` with the PR number, and push the changelog — all without asking the user for additional input. + +If the user has NOT previously run the `/start-pr` command, prompt them for the issue number to work on. + +Required behavior (non-interactive flow) + +1. Read the spec for the given issue in `specs/` and determine the next incomplete section from the Task List. +2. For each task in the incomplete section: + - Implement the task. + - Run the relevant automated tests immediately after implementing the change. Tests must be run and pass before committing. Typical commands to run are: + - Unit/contract/integration (with race): + - `go test -race ./tests/unit ./tests/contract ./tests/integration` + - Performance tests (optional, without race): + - `go test ./tests/performance` + - If a change only affects unit tests, run the narrower set of packages to save time. + - If tests fail, refine the code until tests pass. Do not proceed to committing that TODO item until its tests pass. + - Once tests pass, update the spec (check off corresponding item) and commit the change locally using a descriptive conventional commit message (example `feat(7): add backup script`). + - Use: `git add -A && git commit -m ": "` +3. After all task items for the current section are completed and committed locally: + - Push the branch to the remote: + - `git push -u origin "$(git rev-parse --abbrev-ref HEAD)"` + - Create the pull request non-interactively using `gh` (GitHub CLI). Provide a clear title and a PR body via a HEREDOC to avoid shell quoting issues. Example: + - `gh pr create --title "" --body "$(cat <<'EOF'\n\nEOF\n)"` + - Retrieve the created PR number and URL to update `CHANGELOG.md`. + - Example to get the PR number: `gh pr view --json number,url --jq '.number'` + - Update `CHANGELOG.md` at the top (under the current unreleased section) with a single entry referencing the PR number in the repository style, for example: + - `- [#NN](https://github.com/kwila-cloud/simple-sync/pull/NN): Short description` + - Commit the `CHANGELOG.md` update and push the commit (it must be on the same branch so the changelog change is included in the PR): + - `git add CHANGELOG.md && git commit -m "chore: add changelog entry for PR #NN" && git push` + - Ask the user for code review feedback on the new pull request. + - DO NOT suggest starting on the next section of the task list. + +Error handling and constraints + +- The command must NOT prompt the user for extra confirmation during the flow. If an operation would normally require input (for example, `gh pr create` in interactive mode), invoke the non-interactive flags and provide the input programmatically (HEREDOC or CLI flags). +- If network push or GH CLI operations fail, surface the error and abort; do not attempt destructive recovery automatically. -1. Use the `todoread` tool to get the TODO list for the current issue -1. For each item in the TODO list: - 1. Carefully implement change - 1. Run relevant automated tests - 1. Refine based on test results - 1. Check off corresponding item in the issue spec - 1. Commit -1. After all items on the TODO list are complete: - 1. Push - 1. Create pull request - 1. Update changelog - 1. Push diff --git a/.opencode/command/start-pr.md b/.opencode/command/start-pr.md index 47d4d87..da9cfaa 100644 --- a/.opencode/command/start-pr.md +++ b/.opencode/command/start-pr.md @@ -11,17 +11,17 @@ Use the user input as the issue number. If the user input is empty or invalid, prompt the user for the issue number. -1. Check the spec for the given issue in `specs/` -1. Determine the next incomplete section from the Task List -1. Create branch using format `{issue-number}-{section-name}` - - DO NOT create branch if already in the correct branch for the issue number and section name -1. Research codebase to understand what's needed for the section -1. Ask the user any clarifying questions needed to implement the section -1. Create a TODO list with the `todowrite` tool -1. Explain the TODO list to the user -1. Refine TODO list based on user feedback -1. Prompt the user to run `/do-pr` when they are ready +Required behavior and confirmation flow -Example usage: -- User: `/start-pr 7` -- Agent: Checks `specs/7-data-persistence.md`, finds next incomplete section, creates branch like `7-storage-interface-updates`, researches requirements, explains plan +1. Read the spec for the given issue in `specs/` and determine the next incomplete section from the Task List. +2. Branch creation rules: + - Create a new branch only when the current branch name does **not** already match the desired `{issue-number}-{section-name}` for the section. + - If the current branch already matches the section, do not create or switch branches. + - If the user explicitly requests to stay on the current branch, do not create a branch. +3. Research the codebase to gather information about the change. +4. Ask the user clarifying questions. + - Clearly number the questions. + - Clearly letter the options for each question. +5. Update the Task List section with any new updates based on your research and the user's answers. +6. Explain the current Task List section to the user. +7. When the Task List section is approved by the user, instruct the user to run `/do-pr` to begin implementing the changes. Do not use the word “proceed” as the final prompt — always reference `/do-pr`. diff --git a/AGENTS.md b/AGENTS.md index 6df0445..9c273ba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -141,31 +141,61 @@ See `specs/7-data-persistence.md` for a well-structured specification that: - **Recommendation:** When programmatically constructing search patterns, either validate the regex before use or default to fixed-string searches. If you are unsure whether a pattern contains regex metacharacters, use `-F` to avoid surprises. +### PR Title & Description Rules +- **Always inspect the full diff for the branch before creating a PR.** Use Git to view changes against the base branch and confirm the final, combined diff that will become the PR. + + - View commits on your branch relative to `main`: + - `git fetch origin && git log --oneline origin/main...HEAD` + - View a concise file/status diff against `main`: + - `git fetch origin && git diff --name-status origin/main...HEAD` + - View a human-readable patch summary before creating the PR: + - `git fetch origin && git diff --stat origin/main...HEAD` + - View the full patch if needed: + - `git fetch origin && git diff origin/main...HEAD` + +- **Title rules:** + - The PR title must be a short, descriptive summary of the change and MUST NOT include the issue number. + - Example — incorrect: `docs(7): documentation and restore script fixes for data persistence` + - Example — correct: `docs: documentation and restore script fixes for data persistence` + +- **Description rules:** + - The PR body/description MUST include the issue number (and link) that the PR addresses. Prefer an explicit `Fixes #` or `Related to #` line. + - Example body snippet: + + Issue: https://github.com/kwila-cloud/simple-sync/issues/7 + + This PR moves the data-persistence doc into the site content and adds a checklist to verify `restore.sh` behavior. + + Fixes #7 + +- **How to get the final PR number non-interactively:** + - After creating the PR, capture the assigned number: + - `gh pr view --json number,url --jq '.number'` (use the branch name if needed) + ### Changelog -- **ALWAYS add a new line to `CHANGELOG.md` for each new *pull request* (PR).** Do not link to the issue number — reference the PR number. -- Document new features, enhancements, bug fixes, and breaking changes -- Follow the existing format with PR links and clear descriptions (see examples below) -- Keep entries concise but descriptive for users and maintainers -- **IMPORTANT**: Always verify the actual PR details before updating the changelog. Use `gh pr view ` or `gh pr view ` to confirm the PR number, title, and changed files. +- **ALWAYS add a new line to `CHANGELOG.md` for each new *pull request* (PR).** Do not include or link to the issue number — reference the PR number only. +- Document new features, enhancements, bug fixes, and breaking changes. +- Follow the existing format with PR links and clear descriptions (see examples below). +- Keep entries concise but descriptive for users and maintainers. +- **IMPORTANT**: Always verify the actual PR details **and the PR title** before updating the changelog. Use `gh pr view ` or `gh pr view ` to confirm the PR number, title, and changed files. - **CRITICAL**: Add exactly ONE entry per PR. Never add multiple entries for the same pull request, even if the PR contains multiple types of changes. Combine all changes into a single, concise description. Examples (incorrect vs correct): -- Incorrect (links to issue instead of PR): +- Incorrect (links to issue rather than PR, or includes issue numbers in the text): - `- [#7](https://github.com/kwila-cloud/simple-sync/issues/7): Implement ACL rule storage (CreateAclRule, GetAclRules)` -- Correct (match existing style — use PR link and number): +- Correct (match existing style — use PR link and number, no issue numbers in entry): - `- [#59](https://github.com/kwila-cloud/simple-sync/pull/59): Implement ACL rule storage (CreateAclRule, GetAclRules)` Recommended workflow to avoid mistakes: -1. Create the PR on the branch (`gh pr create`), or push the branch and open the PR on GitHub. -2. Immediately run `gh pr view --json number,url,title --jq '.number'` or `gh pr view ` to retrieve the assigned PR number. - - Example: `gh pr view 7-acl-rule-storage --json number,url,title` -3. Edit `CHANGELOG.md` at the top (under the current unreleased version) and add a single line using the PR number as shown in the "Correct" example above. -4. Commit the changelog update to the same branch so the changelog change is included in the PR. -5. Push the branch and verify the PR body/changed files if necessary. +1. Inspect the full diff using the `git` commands above and confirm the intended changes. +2. Create the PR non-interactively using `gh pr create` with a title (no issue number) and a body that includes the issue link/number. +3. Immediately run `gh pr view --json number,url,title --jq '.number'` to get the PR number. +4. Update `CHANGELOG.md` at the top (under the current unreleased version) with a single entry referencing the **PR number only**. +5. Commit the changelog update to the same branch so the changelog change is included in the PR. Notes: - The changelog should always reference the PR number (not the issue number) because the PR is the canonical unit that contains the implemented changes and the exact diff reviewers will see. @@ -195,7 +225,6 @@ See `.opencode/command/` directory for examples. ### Idiomtic Go - #### Looping Prefer `for i := range n` (Go 1.22+) over `for i := 0; i < n; i++` when iterating a fixed integer count; prefer `for i := range slice` when iterating slices. @@ -223,3 +252,9 @@ Prefer `for i := range n` (Go 1.22+) over `for i := 0; i < n; i++` when iteratin - ❌ Bad: Creating custom string formatting helpers 4. **When in doubt**: If you're about to write a helper function, first check the Go standard library documentation for existing solutions + +### SQLite Storage +- The project uses SQLite for persistent storage; production/dev processes expect a local file under `./data` by default (`./data/simple-sync.db`). +- Docker Compose is configured to bind-mount `./data` into the container (`./data:/app/data`) so the DB file is stored on the host. This is the recommended setup for development and simple deployments because it makes backups and inspection straightforward. +- If you need a Docker-managed volume, a named volume `simple-sync-data` exists in `docker-compose.yml` (commented). Using a named volume is fine for environments where host access is not required, but it makes manual backups/restores less obvious. + diff --git a/CHANGELOG.md b/CHANGELOG.md index 882a46a..b1fbd8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Release History ## [0.4.0] - unreleased +- [#62](https://github.com/kwila-cloud/simple-sync/pull/62): Add storage documentation - [#61](https://github.com/kwila-cloud/simple-sync/pull/61): Add performance and concurrency tests for SQLite storage - [#60](https://github.com/kwila-cloud/simple-sync/pull/60): Implement SQLite setup token and API key storage - [#59](https://github.com/kwila-cloud/simple-sync/pull/59): Implement SQLite ACL rule storage diff --git a/README.md b/README.md index 729605c..7b9abd4 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,27 @@ services: - simple-sync ``` +### Persistent Data (Docker Compose) +The Docker Compose configuration mounts a local `./data` directory into the container (`./data:/app/data`) by default. This is the recommended setup for development and simple deployments because it keeps the SQLite database file on the host, making backups and inspection straightforward. + +If you prefer Docker-managed storage, a named volume `simple-sync-data` is declared in `docker-compose.yml`; you can switch to it by uncommenting the named volume line and removing the `./data` bind mount. + +Backup and restore helper scripts are provided in `./scripts`: +- `./scripts/backup.sh [--stop] [--dir ] [path-to-db]` — copy the DB file to `./backups/` (or specified directory) (use `--stop` to stop the container during backup) +- `./scripts/restore.sh [--stop]` — restore a backup into `./data/simple-sync.db` (moves the existing DB aside first) + +Example (take a backup and then start): + +```bash +# create a backup (stop service during the copy) +./scripts/backup.sh --stop +# start services +docker compose up -d +``` + +Developer note: the app uses `github.com/mattn/go-sqlite3` which requires `libsqlite3-dev` and `CGO_ENABLED=1` when building locally or in CI. + + ## Development ### Building diff --git a/docker-compose.yml b/docker-compose.yml index dc573db..017118d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,9 @@ services: environment: - PORT=8080 volumes: - - ./data:/app/data + - ./data:/app/data # explicit local bind for persistent DB file (recommended for development) + # Alternative: use a named volume managed by Docker + # - simple-sync-data:/app/data restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "-s", "http://localhost:8080/api/v1/health"] diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100644 index 0000000..532daa8 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Simple Sync backup script +# Usage: ./scripts/backup.sh [--stop] [--dir ] [path-to-db] +# If --stop is provided, the script will stop the docker-compose service before copying and restart it afterwards. +# If --dir is provided, it specifies the directory to store backups (defaults to ./backups). + +STOP=false +BACKUP_DIR="./backups" +DB_PATH="./data/simple-sync.db" +DB_PATH_PROVIDED=false + +while [ "$#" -gt 0 ]; do + case "$1" in + --stop) + STOP=true + shift + ;; + --dir) + if [ -z "${2:-}" ]; then + echo "Error: --dir requires a directory argument" + exit 2 + fi + BACKUP_DIR="$2" + shift 2 + ;; + --dir=*) + BACKUP_DIR="${1#--dir=}" + shift + ;; + --help|-h) + echo "Usage: $0 [--stop] [--dir ] [path-to-db]" + exit 0 + ;; + *) + if [ "$DB_PATH_PROVIDED" = false ]; then + DB_PATH="$1" + DB_PATH_PROVIDED=true + shift + else + echo "Unknown argument: $1" + exit 2 + fi + ;; + esac +done + +mkdir -p "$BACKUP_DIR" + +if [ ! -f "$DB_PATH" ]; then + echo "Error: database file not found at $DB_PATH" + exit 1 +fi + +TIMESTAMP=$(date +%Y%m%d%H%M%S) +BACKUP_FILE="$BACKUP_DIR/simple-sync-$TIMESTAMP.db" + +if [ "$STOP" = true ]; then + echo "Stopping simple-sync service..." + docker compose stop simple-sync || true +fi + +echo "Copying $DB_PATH -> $BACKUP_FILE" +cp -- "$DB_PATH" "$BACKUP_FILE" + +if [ "$STOP" = true ]; then + echo "Starting simple-sync service..." + docker compose start simple-sync || true +fi + +echo "Backup complete: $BACKUP_FILE" +exit 0 diff --git a/scripts/restore.sh b/scripts/restore.sh new file mode 100644 index 0000000..8ed2bc3 --- /dev/null +++ b/scripts/restore.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Simple Sync restore script +# Usage: ./scripts/restore.sh [--stop] +# Restores the given backup file into ./data/simple-sync.db. If --stop is provided, the service will be stopped before restore and started after. + +if [ "$#" -lt 1 ]; then + echo "Usage: $0 [--stop]" + exit 2 +fi + +BACKUP_FILE="$1" +shift + +STOP=false +if [ "${1:-}" = "--stop" ]; then + STOP=true +fi + +DB_DIR="./data" +DB_PATH="$DB_DIR/simple-sync.db" + +if [ ! -f "$BACKUP_FILE" ]; then + echo "Error: backup file not found: $BACKUP_FILE" + exit 1 +fi + +mkdir -p "$DB_DIR" + +if [ "$STOP" = true ]; then + echo "Stopping simple-sync service..." + docker compose stop simple-sync || true +fi + +# Safety: move existing DB aside +if [ -f "$DB_PATH" ]; then + OLD_TS=$(date +%Y%m%d%H%M%S) + mv -- "$DB_PATH" "$DB_DIR/simple-sync.db.bak.$OLD_TS" + echo "Moved existing DB to $DB_DIR/simple-sync.db.bak.$OLD_TS" +fi + +cp -- "$BACKUP_FILE" "$DB_PATH" + +if [ "$STOP" = true ]; then + echo "Starting simple-sync service..." + docker compose start simple-sync || true +fi + +echo "Restore complete: $DB_PATH" +exit 0 diff --git a/specs/7-data-persistence.md b/specs/7-data-persistence.md index fc6d001..ae9daf1 100644 --- a/specs/7-data-persistence.md +++ b/specs/7-data-persistence.md @@ -79,11 +79,11 @@ Encryption at rest will be addressed separately (issue #17) using SQLCipher or f - [x] Validate concurrent access and large dataset handling ### Documentation and Configuration Updates -- [ ] Update Docker configuration for data persistence -- [ ] Update AGENTS.md with SQLite storage information -- [ ] Update README.md with data persistence features and setup instructions -- [ ] Update user-facing documentation in docs/ with SQLite configuration -- [ ] Document backup/restore procedures and security considerations +- [x] Update Docker configuration for data persistence +- [x] Update AGENTS.md with SQLite storage information +- [x] Update README.md with data persistence features and setup instructions +- [x] Update user-facing documentation in `src/content/docs` with SQLite configuration +- [x] Document backup/restore ### Clean Up - [ ] Handle remaining TODO comments for issue #7 diff --git a/src/content/docs/data-persistence.mdx b/src/content/docs/data-persistence.mdx new file mode 100644 index 0000000..1228a7e --- /dev/null +++ b/src/content/docs/data-persistence.mdx @@ -0,0 +1,38 @@ +--- +title: Data Persistence +--- + +## Data Persistence and Backups + +This project uses SQLite for persistent storage. By default the service stores its database file in `./data/simple-sync.db` when you run via Docker Compose. + +### Running with Docker Compose + +The repository ships a `docker-compose.yml` that bind-mounts `./data` into the container (`./data:/app/data`). This keeps the DB file on the host so you can inspect, back up, or move it easily. + +### Backups + +Use the supplied helper scripts: + +- `./scripts/backup.sh [--stop] [--dir ] [path-to-db]` — copies the DB file to `./backups/` (or specified directory) with a timestamped filename. Use `--stop` to stop the container during backup for safety. +- `./scripts/restore.sh [--stop]` — restores a backup into `./data/simple-sync.db`. The existing DB (if any) will be moved aside before restore. + +Example workflow: + +```bash +# Stop, backup, and start using the scripts +./scripts/backup.sh --stop +docker compose up -d +``` + +### Security Considerations + +- The database file contains sensitive data. Store backups securely and limit filesystem access. +- For production, consider encrypting the filesystem or using SQLCipher (see issue #17). +- Ensure regular backups and test restores periodically. + +### Developer notes + +- Building the binary with `github.com/mattn/go-sqlite3` requires a C toolchain and headers (`libsqlite3-dev`) and `CGO_ENABLED=1`. + +