commit c52353ac8e11cabdbc20e6346164630dc05b1455 Author: Mischa Date: Wed Jun 17 23:38:59 2026 +0200 Initial project setup: Docker, Makefile, scripts, plugins diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fab0e37 --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# SSH connection +REMOTE_USER=root +REMOTE_HOST=example.com +REMOTE_PORT=22 +REMOTE_HOME=/home/example.com + +# Server paths (override here if your setup differs from the Makefile defaults) +WEBROOT=/home/example.com/public_html +SITE_CONFIG_DIR=/home/example.com/site-config + +# Grav +GRAV_VERSION=1.7.52 + +# Repos +USER_REPO=https://gitea.example.com/org/intotheeast-user.git +MAIN_REPO=https://gitea.example.com/org/travel-blog-intotheeast.git + +# Gitea credentials — never commit these; only ever in .env (local) or ~/.env-project (server, temporary) +GITEA_HOST=gitea.example.com +GITEA_USER=deploy-user +GITEA_TOKEN=your-gitea-personal-access-token diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2448cf0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Environment +.env + +# Grav CMS +/user/ +user/accounts/ +user/data/ +user/cache/ +user/plugins/ + +# Claude +.claude/ + +# OS +.DS_Store diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..32450d9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,29 @@ +# CLAUDE.md + +## 0. Project specifics + +**Only ever write changes in this folder (travel-blog-intotheeast/) or its subfolders.** + +### Folder explanation + +- **./**: Grav CMS dev environment for intotheeast travel blog +- **scripts/**: Server install and maintenance scripts +- **user/**: Site content, config, pages, and theme (standalone git repo — do not modify from here) + +### Environment + +**Never read `.env`** — it contains sensitive credentials. You may pass it to commands (e.g. `docker compose`, `make`) but never read its contents directly. Ask the user if you need environment-specific information. + +### Remote operations + +Always use `make` commands for anything on the production server (`make remote-install-plugins`, `make remote-clean`, etc.) — never SSH directly since credentials live in `.env`. If a remote operation isn't covered by an existing `make` command, either ask the user to run it manually or suggest adding a new `make` command if it seems reusable. + +### Content sync + +- `make content-push` — commit and push `user/` to Gitea (triggers production pull via webhook) +- `make content-pull` — pull latest from Gitea to local +- `plugins.txt` is manually maintained — installing a plugin via Admin does NOT update it + +### User repo gitignore + +Only these folders are tracked in the `user/` Git repo: `pages/`, `config/`, `accounts/`, `themes/`. The `plugins/` and `data/` folders are excluded. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..008f719 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +-include .env +export + +REMOTE_PORT ?= 22 +SSH := ssh -p $(REMOTE_PORT) $(REMOTE_USER)@$(REMOTE_HOST) +WEBROOT ?= $(REMOTE_HOME)/public_html +SITE_CONFIG_DIR ?= $(REMOTE_HOME)/site-config + +# ── Local dev ────────────────────────────────────────────────────────────────── + +start: + docker compose up -d + +stop: + docker compose down + +setup: start install-plugins + +install-plugins: + docker exec intotheeast_grav php /app/www/public/bin/gpm install $(shell cat plugins.txt | tr '\n' ' ') -y + +# ── Content sync (user repo ↔ Gitea) ────────────────────────────────────────── + +content-push: + git -C user push origin main + +content-pull: + git -C user pull origin main + +# ── Remote credentials ───────────────────────────────────────────────────────── + +remote-env-setup: + @$(SSH) "printf 'GITEA_HOST=%s\nGITEA_USER=%s\nGITEA_TOKEN=%s\n' \ + '$(GITEA_HOST)' '$(GITEA_USER)' '$(GITEA_TOKEN)' > ~/.env-intotheeast && chmod 600 ~/.env-intotheeast" + @echo "Credentials written to server. Run 'make remote-env-remove' when done." + +remote-env-remove: + @$(SSH) "rm -f ~/.env-intotheeast" + @echo "Credentials removed from server." + +# ── Remote: initial install ──────────────────────────────────────────────────── + +remote-wipe: + $(SSH) "cd $(WEBROOT) && rm -rf assets backup bin cache images logs system tmp vendor webserver-configs index.php .htaccess CHANGELOG.md LICENSE.txt README.md" + +remote-install: + $(SSH) "WEBROOT=$(WEBROOT) \ + SITE_CONFIG_DIR=$(SITE_CONFIG_DIR) \ + USER_REPO=$(USER_REPO) \ + MAIN_REPO=$(MAIN_REPO) \ + GRAV_VERSION=$(GRAV_VERSION) \ + PLUGINS='$(shell cat plugins.txt | tr '\n' ' ')' \ + GITEA_HOST=$(GITEA_HOST) \ + GITEA_USER=$(GITEA_USER) \ + GITEA_TOKEN=$(GITEA_TOKEN) \ + bash -s" < scripts/server-install.sh + +# ── Remote: ongoing maintenance ──────────────────────────────────────────────── + +remote-fetch: + $(SSH) "git -C $(SITE_CONFIG_DIR) pull" + +remote-install-plugins: + $(SSH) "cd $(WEBROOT) && php bin/gpm install $(shell cat plugins.txt | tr '\n' ' ') -y" + +remote-upgrade-grav: + $(SSH) "cd $(WEBROOT) && php bin/grav upgrade" + +remote-clean: + $(SSH) "cd $(WEBROOT) && php bin/grav clearcache" + +remote-maintenance-on: + $(SSH) "bash -s on $(WEBROOT)" < scripts/server-maintenance.sh + +remote-maintenance-off: + $(SSH) "bash -s off $(WEBROOT)" < scripts/server-maintenance.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..1414679 --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +# into the east — Grav CMS + +Grav CMS travel blog. Local dev via Docker; production on a VPS managed entirely through `make`. + +--- + +## Repository structure + +Two git repos: + +| Repo | Contents | Location | +|------|----------|----------| +| `intotheeast.com` (this repo) | Docker setup, Makefile, scripts, plugins.txt | `./` | +| `intotheeast.com-content` | Site config, pages, theme | `user/` (standalone git repo) | + +The `user/` directory is a standalone git repo — its changes are pushed/pulled independently to Gitea. The Grav Sync plugin on the server automatically pulls from Gitea when content is pushed. + +--- + +## Prerequisites + +- Docker (for local dev) +- SSH access to the production server +- Both Gitea repos created and accessible +- A Gitea personal access token with repo read/write access + +--- + +## Local development setup + +```bash +cp .env.example .env # fill in your values — never commit this file +make setup # start Docker container and install plugins +``` + +Site runs at http://localhost:8081. + +Clone the user content repo into `user/` if not already present: + +```bash +git clone $USER_REPO user/ +``` + +--- + +## First-time server setup + +**1. Fill in `.env`** — copy `.env.example` and set all values including `REMOTE_USER`, `REMOTE_HOST`, `USER_REPO`, `MAIN_REPO`, and Gitea credentials. + +**2. Run the install:** + +```bash +make remote-install +``` + +This SSHes into the server, downloads Grav, clones both repos (user content + this config repo), installs plugins, and prints the server's SSH public key. + +**3. Add the SSH key to Gitea** — copy the printed public key and add it as a read-only deploy key to both Gitea repos. After this, `make remote-fetch` works without credentials. + +--- + +## Content sync workflow + +To pull editor changes locally: + +```bash +make content-pull # pull latest user/ content from Gitea → local +``` + +To push local changes to Gitea (triggers server sync): + +```bash +git -C user add -A && git -C user commit -m "content: describe change" +make content-push # push local user/ commits → Gitea +``` + +--- + +## All commands + +### Local + +| Command | Description | +|---------|-------------| +| `make start` | Start the local Docker container | +| `make stop` | Stop the local Docker container | +| `make setup` | Start container and install all plugins from plugins.txt | +| `make install-plugins` | (Re)install plugins from plugins.txt in the local container | +| `make content-push` | Push local `user/` commits to Gitea | +| `make content-pull` | Pull latest `user/` content from Gitea | + +### Remote credentials + +| Command | Description | +|---------|-------------| +| `make remote-env-setup` | Write Gitea credentials to `~/.env-intotheeast` on the server | +| `make remote-env-remove` | Delete `~/.env-intotheeast` from the server | + +Always run `make remote-env-remove` when done. Credentials must not persist on the server. + +### Remote server management + +| Command | Description | +|---------|-------------| +| `make remote-install` | First-time install: download Grav, clone both repos, install plugins | +| `make remote-fetch` | Pull latest config repo (Makefile, scripts, plugins.txt) on the server | +| `make remote-install-plugins` | Install/update plugins from local plugins.txt on the server | +| `make remote-upgrade-grav` | Upgrade Grav core on the server | +| `make remote-clean` | Clear Grav cache on the server | +| `make remote-maintenance-on` | Enable maintenance mode (visitors see offline page) | +| `make remote-maintenance-off` | Disable maintenance mode | + +### Typical upgrade workflow + +```bash +make remote-maintenance-on +make remote-upgrade-grav +make remote-install-plugins +make remote-clean +make remote-maintenance-off +``` + +--- + +## Plugins + +Plugins are not committed to git. The full list is in `plugins.txt` — one plugin name per line. + +- Locally: `make install-plugins` +- On server: `make remote-install-plugins` + +--- + +## Security + +- `.env` is gitignored. Never commit it — it contains your server credentials and Gitea token. +- `GITEA_TOKEN` exists only in `.env` locally, and in `~/.env-intotheeast` on the server only during active sessions. Always run `make remote-env-remove` after use. +- `~/.env-intotheeast` has `chmod 600` — readable only by the SSH user. +- The server pulls from Gitea using its SSH deploy key (read-only). No long-lived token is stored on the server after initial install. +- `scripts/server-install.sh` writes `~/.netrc` for the initial clone only, and deletes it immediately after via a `trap` handler — even if the script fails. +- Credentials are never passed as command-line arguments (they would appear in server process listings). They are passed as environment variables within the SSH session. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..521bd5d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +services: + grav: + image: lscr.io/linuxserver/grav:latest + container_name: intotheeast_grav + environment: + - PUID=1000 + - PGID=1000 + ports: + - "8081:80" + volumes: + - ./user:/config/www/user + restart: unless-stopped diff --git a/plugins.txt b/plugins.txt new file mode 100644 index 0000000..001b800 --- /dev/null +++ b/plugins.txt @@ -0,0 +1,6 @@ +admin +email +error +form +login +problems diff --git a/scripts/server-install.sh b/scripts/server-install.sh new file mode 100755 index 0000000..f891a53 --- /dev/null +++ b/scripts/server-install.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -e + +: "${WEBROOT:?WEBROOT is not set}" +: "${SITE_CONFIG_DIR:?SITE_CONFIG_DIR is not set}" +: "${USER_REPO:?USER_REPO is not set}" +: "${MAIN_REPO:?MAIN_REPO is not set}" +: "${GRAV_VERSION:?GRAV_VERSION is not set}" +: "${PLUGINS:?PLUGINS is not set}" +: "${GITEA_HOST:?GITEA_HOST is not set}" +: "${GITEA_USER:?GITEA_USER is not set}" +: "${GITEA_TOKEN:?GITEA_TOKEN is not set}" + +trap 'rm -f ~/.netrc' EXIT + +echo "==> Setting up credentials (temporary)" +printf 'machine %s\nlogin %s\npassword %s\n' "$GITEA_HOST" "$GITEA_USER" "$GITEA_TOKEN" > ~/.netrc +chmod 600 ~/.netrc + +echo "==> Downloading Grav $GRAV_VERSION" +cd "$WEBROOT" +wget --no-verbose "https://getgrav.org/download/core/grav-admin/$GRAV_VERSION" -O grav-admin.zip +unzip -oq grav-admin.zip +cp -rf grav-admin/. . +rm -rf grav-admin grav-admin.zip + +echo "==> Cloning user repo" +rm -rf user +git clone "$USER_REPO" user + +echo "==> Cloning main config repo to $SITE_CONFIG_DIR" +if [ -d "$SITE_CONFIG_DIR/.git" ]; then + git -C "$SITE_CONFIG_DIR" pull +else + rm -rf "$SITE_CONFIG_DIR" + git clone "$MAIN_REPO" "$SITE_CONFIG_DIR" +fi + +echo "==> Creating required directories" +mkdir -p user/plugins user/accounts user/data + +echo "==> Installing plugins" +php bin/gpm install $PLUGINS -y + +echo "==> Setting permissions" +find "$WEBROOT" -type f -exec chmod 664 {} \; +find "$WEBROOT" -type d -exec chmod 775 {} \; + +echo "==> Removing temporary credentials" +rm -f ~/.netrc + +echo "" +echo "==> Done." +echo "" +echo "NEXT STEP — add this server's SSH public key to both Gitea repos as a deploy key" +echo "so that 'make remote-fetch' and future git pulls work without credentials:" +echo "" +cat ~/.ssh/id_rsa.pub 2>/dev/null || cat ~/.ssh/id_ed25519.pub 2>/dev/null || \ + echo " No SSH key found. Generate one on the server: ssh-keygen -t ed25519 -C 'server-deploy'" +echo "" +echo "Visit your domain to complete Grav setup." diff --git a/scripts/server-maintenance.sh b/scripts/server-maintenance.sh new file mode 100755 index 0000000..760ef1d --- /dev/null +++ b/scripts/server-maintenance.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +if [ "$#" -ne 2 ]; then + echo "Usage: server-maintenance.sh on|off " + exit 1 +fi + +MODE="$1" +WEBROOT="$2" +CONFIG="$WEBROOT/user/config/system.yaml" + +if [ "$MODE" != "on" ] && [ "$MODE" != "off" ]; then + echo "Usage: server-maintenance.sh on|off " + exit 1 +fi + +[ -f "$CONFIG" ] || { echo "Not found: $CONFIG"; exit 1; } + +VALUE="false" +[ "$MODE" = "on" ] && VALUE="true" + +if grep -q "^[[:space:]]*offline:" "$CONFIG"; then + sed -i "s/^\([[:space:]]*\)offline: .*/\1offline: $VALUE/" "$CONFIG" +else + printf '\npages:\n offline: %s\n' "$VALUE" >> "$CONFIG" +fi + +echo "Maintenance mode: $MODE (offline: $VALUE)" diff --git a/travel-blog-intotheeast.code-workspace b/travel-blog-intotheeast.code-workspace new file mode 100644 index 0000000..362d7c2 --- /dev/null +++ b/travel-blog-intotheeast.code-workspace @@ -0,0 +1,7 @@ +{ + "folders": [ + { + "path": "." + } + ] +} \ No newline at end of file