diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..d1b2554a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,44 @@ +--- +applyTo: '**' +--- +# Project Instructions + +`stack` is the Docker Swarm configuration for [vibetype.app](https://vibetype.app/), an event community platform, managed with [dargstack](https://github.com/dargstack/dargstack/). + +## Documentation Map + +**For understanding the stack structure and deployment:** +- [README.md](README.md): Project overview, quick start +- [artifacts/docs/README.md](artifacts/docs/README.md): Auto-generated service, secret and volume reference (do not edit manually) + +**For contributing:** +- [CONTRIBUTING.md](CONTRIBUTING.md): Development setup, dargstack guidelines, code style, git workflow + +## Code Style + +- Do not use abbreviations in naming, except where omitting them would look unnatural +- Use natural language in any non-code text instead of referring to code directly, e.g. "the database's password" instead of "the `postgres_password`", except when a code reference is needed +- Use backticks in any non-code text to refer to code, e.g. "`postgres`" instead of "postgres" +- Sort YAML keys lexicographically except where order is semantically significant +- Code formatting is done by the editor via `.editorconfig` + +## Git + +- Work on branches other than the default branch + - Use this branch naming pattern: `//` +- Git commit titles must follow the Conventional Commits specification and be lowercase only + - The commit scope should not be repeated in the commit description, e.g. `feat(postgres): add role` instead of `feat(postgres): add postgres role` +- Git commit scopes must be chosen as follows (ordered by priority): + 1. service name, e.g. `postgres`, `traefik`, `vibetype` + 2. simplified dependency name, e.g. `dargstack`, `docker` + 3. area, e.g. `secrets`, `volumes`, `certificates` +- Commit bodies are only to be filled in when necessary, e.g. to mention a resolved issue link + +## Docker / dargstack + +- Each service lives under `src/development//compose.yaml` as a full Compose document and optionally `src/production//compose.yaml` as a delta-only override +- Lines annotated with `# dargstack:dev-only` are stripped from production compose +- Run `dargstack build ` after changing a service's source code to rebuild its development container image +- Run `dargstack validate` to check the stack configuration +- Run `dargstack document` to regenerate `artifacts/docs/README.md` (do not edit that file manually) +- Do not commit files from `artifacts/` unless they are tracked (e.g. `artifacts/docs/SERVICES_ADDITIONAL.md`) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..32a53e0c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,130 @@ +# Contributing to stack + +Thank you for your interest in contributing! + +The fullstack environment composes several services, among those are the following first-party: + +| Repository | Required | Profile | Access | +|---|---|---|---| +| [maevsi/android](https://github.com/maevsi/android) | optional | — | public | +| [maevsi/ios](https://github.com/maevsi/ios) | optional | — | public | +| [maevsi/postgraphile](https://github.com/maevsi/postgraphile) | ✅ | `default` | public | +| [maevsi/reccoom](https://github.com/maevsi/reccoom) | optional | `recommendation` | private | +| [maevsi/sqitch](https://github.com/maevsi/sqitch) | ✅ | `default` | public | +| [maevsi/stack](https://github.com/maevsi/stack) | ✅ | — | public | +| [maevsi/vibetype](https://github.com/maevsi/vibetype) | ✅ | `default` | public | + +## Development Setup + +There are two development modes: + +| Mode | When to use | Setup | Where to start | +|---|---|---|---| +| **Frontend only** | Working on UI, i18n, or anything that doesn't require running backend services | Manual only | [Vibetype repository](https://github.com/maevsi/vibetype#development) | +| **Fullstack** | Working on backend services, the database, the API, or any cross-cutting concern | Automated or manual | This guide (continue reading) | + +> 🪟 **Windows users:** Set up [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) first and continue inside the Linux subsystem. +> If you use VS Code, see [VS Code + WSL](https://learn.microsoft.com/en-us/windows/wsl/tutorials/wsl-vscode). + +### Automated Setup + +Clone this repository into the directory where you want the project to live, then run the setup script from inside the cloned `stack` repository: +```sh +cd /path/to/where/you/want/the/project +git clone https://github.com/maevsi/stack.git +cd stack + +```sh +bash scripts/setup.sh +``` + +The script will interactively ask which optional feature sets you want, then: + +1. Clone the selected repositories as siblings inside a `vibetype/` parent directory. +2. Run `dargstack build ` for the selected services to build the required development container images. +3. Deploy the development stack with `dargstack deploy`. + +> Per-repository setup (e.g. Node.js install for `vibetype`) is not yet automated here. Each repository will eventually provide its own `scripts/setup.sh` that this script will invoke. In the meantime, follow the manual steps below for repository-specific preparation. + +### Manual Setup + +If you prefer to step through each action yourself: + +1. Install prerequisites + + 1. [Git](https://git-scm.com/): version control + 2. [Docker](https://docs.docker.com/engine/install/): container runtime + 3. [dargstack](https://github.com/dargstack/dargstack#install): stack management CLI + + +1. Create a parent directory and clone the sibling repositories into it: + + ```sh + mkdir vibetype && cd vibetype + git clone git@github.com:maevsi/android.git # optional + git clone git@github.com:maevsi/ios.git # optional + git clone git@github.com:maevsi/postgraphile.git + git clone git@github.com:maevsi/reccoom.git # optional, private + git clone git@github.com:maevsi/sqitch.git + git clone git@github.com:maevsi/stack.git + git clone git@github.com:maevsi/vibetype.git + ``` + +
+ Click here if you don't have SSH set up (you should!) to use HTTPS URLs instead + + + ```sh + mkdir vibetype && cd vibetype + git clone https://github.com/maevsi/android.git # optional + git clone https://github.com/maevsi/ios.git # optional + git clone https://github.com/maevsi/postgraphile.git + git clone https://github.com/maevsi/reccoom.git # optional, private + git clone https://github.com/maevsi/sqitch.git + git clone https://github.com/maevsi/stack.git + git clone https://github.com/maevsi/vibetype.git + ``` +
+ +2. Initialize all cloned projects for development according to their READMEs. + +3. Build development container images: + + ```sh + cd stack + dargstack build + ``` + + An interactive selection dialog will let you choose which services to build. + +4. Deploy: + + ```sh + dargstack deploy + ``` + +5. You should now be able to access Vibetype at [https://app.localhost](https://app.localhost) 🎉 + + +## Guidelines + +### Git & GitHub + +Follow [@dargmuesli's Contributing Guidelines](https://gist.github.com/dargmuesli/430b7d902a22df02d88d1969a22a81b5#contribution-workflow) for branch naming, commit formatting, and the pull request workflow. + +### Semantic Versioning + +Read [@dargmuesli's guide on Semantic Versioning](https://gist.github.com/dargmuesli/430b7d902a22df02d88d1969a22a81b5#file-semantic-versioning-md) for how to format PR, issue and commit titles. + +### dargstack + +- Service files live in `src/development//compose.yaml` (full Compose document) and `src/production//compose.yaml` (production delta only). +- Run `dargstack build` to interactively select and build development container images after making changes to a service's source code. +- Run `dargstack document` to regenerate `artifacts/docs/README.md` after adding or modifying services. +- Do not edit `artifacts/` files directly: they are generated or gitignored. + +### Code Style + +- Keep YAML keys sorted lexicographically where order is semantically irrelevant. +- Use natural language in comments; refer to code artifacts with backticks. +- Do not use abbreviations in names unless omitting them would look unnatural. diff --git a/README.md b/README.md new file mode 100644 index 00000000..c04389c0 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +[![ci status][ci-image]][ci-url] + +[ci-image]: https://img.shields.io/github/actions/workflow/status/maevsi/stack/ci.yml +[ci-url]: https://github.com/maevsi/stack/actions/workflows/ci.yml + +# @maevsi/stack + +The Docker stack configuration for [vibetype.app](https://vibetype.app/). + +This project is managed with [dargstack](https://github.com/dargstack/dargstack/) and is closely related to [Vibetype's source code](https://github.com/maevsi/vibetype/). + +## Documentation + +To see which services, secrets and volumes this stack includes, head over to [`artifacts/docs/README.md`](artifacts/docs/README.md). + +## Development Setup + +> 💡 **Windows users:** Run these steps inside [WSL](https://docs.microsoft.com/en-us/windows/wsl/install). + +> 📖 **New to the project? Read [CONTRIBUTING.md](CONTRIBUTING.md) first** to understand the repository structure, the development modes, and what each component does. You will need this context to work effectively beyond the quick setup below. + +To start a local fullstack development environment, run the setup script from the directory where you want to clone the project: + +```sh +bash <(curl -fsSL https://raw.githubusercontent.com/maevsi/stack/main/scripts/setup.sh) +``` + +Or, if you have already cloned this repository: + +```sh +bash scripts/setup.sh +``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..a0733052 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,57 @@ +# Vibetype Disclosure Policy + +## 1 Introduction + +The security of Vibetype is a top priority for our team. We believe in transparency and collaboration with the community to identify and address potential security vulnerabilities. This disclosure policy outlines the procedures for reporting security issues in Vibetype and how we handle such reports. + +## 2 Reporting a Security Issue + +If you discover a security vulnerability in Vibetype, we encourage you to report it to us promptly. To report a security issue, please follow these steps: + +Send an email to contact+security@maev.si with the subject line "Security Issue: [Brief Description]." Please provide a detailed description of the vulnerability, including the following: + +- A concise summary of the issue. +- A detailed explanation of the vulnerability and its potential impact. +- Steps to reproduce the vulnerability (if applicable). +- Any additional information that can help us understand and address the issue. + +## 3 Handling of Reports + +Upon receiving a security report, the Vibetype team will follow these steps: + +### 3.1 Acknowledgment + +We will acknowledge your report as soon as possible and provide an estimated timeline for the review and resolution process. + +### 3.2 Evaluation + +Our team will review the reported issue to assess its validity and severity. We may request additional information from you if necessary. + +### 3.3 Resolution + +Once we have confirmed and understood the vulnerability, we will develop and test a fix. We will try to resolve the issue as quickly as possible. + +### 3.4 Communication + +We will keep you informed of our progress throughout the process and notify you when a fix is available. If the issue affects multiple projects, we may coordinate with other project maintainers and vendors. + +## 4 Disclosure + +We believe in responsible disclosure to protect our users. + +If the issue is resolved, we will coordinate with you to establish a mutually agreed-upon release date for the fix. +We will issue a security advisory and update the project's documentation once the fix is publicly available. + +## 5 Credit and Recognition + +We highly value the contributions of the security community and will provide credit to individuals or organizations who responsibly report security vulnerabilities. However, if you prefer to remain anonymous, we will respect your wishes. + +## 6 Legal Protection + +Vibetype will not pursue legal action against security researchers who follow this disclosure policy and act in good faith. We appreciate your efforts to help us maintain the security of our project. + +## 7 Changes to this Policy + +This security disclosure policy may be updated or revised from time to time. + +Last Updated: 2026-04-03 diff --git a/dargstack.yaml b/dargstack.yaml index dd68f610..8e1cc7df 100644 --- a/dargstack.yaml +++ b/dargstack.yaml @@ -25,7 +25,7 @@ behavior: prompt: volume: # Prompt to remove volumes before deploying (development only) - remove: false # default: true + remove: true # Production environment settings production: diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100755 index 00000000..d3354553 --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,203 @@ +#!/usr/bin/env bash +# Bootstrap a local Vibetype fullstack development environment. +# +# Usage (one-shot, without cloning first): +# bash <(curl -fsSL https://raw.githubusercontent.com/maevsi/stack/main/scripts/setup.sh) +# +# Usage (when stack is already cloned): +# bash scripts/setup.sh + +set -euo pipefail + +PARENT_DIR="vibetype" + +# ── helpers ────────────────────────────────────────────────────────────────── + +info() { printf '\033[0;34m➜ %s\033[0m\n' "$*"; } +success() { printf '\033[0;32m✔ %s\033[0m\n' "$*"; } +warn() { printf '\033[0;33m⚠ %s\033[0m\n' "$*"; } +die() { printf '\033[0;31m✖ %s\033[0m\n' "$*" >&2; exit 1; } + +# Ask a yes/no question; defaults to yes on empty input. +confirm() { + local prompt="$1" + local reply + read -r -p "$(printf '\033[0;36m? %s [Y/n] \033[0m' "$prompt")" reply + [[ "${reply:-y}" =~ ^[Yy]$ ]] +} + +require() { + command -v "$1" &>/dev/null || die "'$1' is required but not found. See: $2" +} + +clone_if_missing() { + local repo_url="$1" + local repo_name + repo_name="$(basename "$repo_url" .git)" + if [[ -d "$repo_name/.git" ]]; then + warn "$repo_name already exists, skipping clone." + return + fi + if [[ -d "$repo_name" ]]; then + die "Destination directory '$repo_name' already exists but is not a git repository. Remove or rename it, then rerun setup." + fi + + # Prefer SSH; fall back to HTTPS if SSH authentication fails. + local ssh_url="git@github.com:${repo_url#https://github.com/}" + info "Cloning $repo_name …" + if git clone "$ssh_url" 2>/dev/null; then + return + fi + warn "SSH clone failed for $repo_name. Falling back to HTTPS." + if git clone "$repo_url"; then + return + fi + die "Failed to clone $repo_url via SSH and HTTPS." +} + +# ── prerequisites ───────────────────────────────────────────────────────────── + +info "Step 1/4 — Checking prerequisites (git, docker, dargstack) …" + +require git "https://git-scm.com/" +require docker "https://docs.docker.com/engine/install/" + +# Check that the Docker daemon is actually reachable. +if ! docker info &>/dev/null; then + die "Docker is installed but not running. Start Docker and try again." +fi + +if ! command -v dargstack &>/dev/null; then + if command -v go &>/dev/null; then + info "Installing dargstack via go install …" + go install github.com/dargstack/dargstack/v4/cmd/dargstack@latest + + # Check if dargstack is now in PATH; if not, try adding GOPATH/bin + if ! command -v dargstack &>/dev/null; then + go_bin="$(go env GOPATH)/bin" + if [[ -f "$go_bin/dargstack" ]]; then + export PATH="$go_bin:$PATH" + info "Added $go_bin to PATH for this session." + else + die "dargstack installation completed, but binary not found at $go_bin/dargstack. + Please ensure \$(go env GOPATH)/bin is in your PATH and restart the script. + Or install a pre-built binary from https://github.com/dargstack/dargstack/releases" + fi + fi + else + die "'dargstack' is not installed and Go was not found. + Install Go from https://go.dev/doc/install, then run: + go install github.com/dargstack/dargstack/v4/cmd/dargstack@latest + Or install a pre-built binary from https://github.com/dargstack/dargstack/releases" + fi +fi + +# ── detect working directory ────────────────────────────────────────────────── + +CURRENT_DIR="$(pwd)" +if [[ "$(basename "$CURRENT_DIR")" == "stack" && -f "$CURRENT_DIR/dargstack.yaml" ]]; then + TARGET_DIR="$(dirname "$CURRENT_DIR")" + info "Running from inside the stack repo. Using parent directory: $TARGET_DIR" +else + TARGET_DIR="$CURRENT_DIR/$PARENT_DIR" + info "Creating project directory: $TARGET_DIR" + mkdir -p "$TARGET_DIR" +fi + +# ── profile / feature selection ─────────────────────────────────────────────── + +printf '\n\033[1mWhich optional feature sets do you want to set up?\033[0m\n' +printf '(The default profile (vibetype, postgres, postgraphile, sqitch, traefik, etc.) is always included.)\n\n' + +CLONE_APP=false +CLONE_RECCOOM=false +EXTRA_PROFILES=() + +if confirm "mobile app development (android, ios)"; then + CLONE_APP=true +fi + +if confirm "recommendation service (requires ssh authentication)"; then + CLONE_RECCOOM=true + EXTRA_PROFILES+=("recommendation") +fi + +printf '\n' + +# ── clone repositories ──────────────────────────────────────────────────────── + +info "Step 2/4 — Cloning repositories …" + +cd "$TARGET_DIR" + +# Always required +clone_if_missing "https://github.com/maevsi/postgraphile.git" +clone_if_missing "https://github.com/maevsi/sqitch.git" +clone_if_missing "https://github.com/maevsi/stack.git" +clone_if_missing "https://github.com/maevsi/vibetype.git" + +# Optional +if $CLONE_APP; then + clone_if_missing "https://github.com/maevsi/android.git" + clone_if_missing "https://github.com/maevsi/ios.git" +fi + +if $CLONE_RECCOOM; then + clone_if_missing "https://github.com/maevsi/reccoom.git" +fi + +success "Repositories ready." + +# ── per-repo setup ──────────────────────────────────────────────────────────── + +info "Step 3/4 — Per-repository setup …" +info " Each repository may need its own initialisation (e.g. installing Node.js dependencies for vibetype)." +info " This step is not yet automated. If you want to start development in a specific repository, you have to manually set it up according to the project's README instructions first." +# TODO: Once each cloned repository ships its own `scripts/setup.sh`, invoke +# them here uniformly instead of repository-specific logic. For example: +# +# for repo in postgraphile sqitch stack vibetype reccoom android ios; do +# if [[ -f "$TARGET_DIR/$repo/scripts/setup.sh" ]]; then +# info "Running $repo setup …" +# bash "$TARGET_DIR/$repo/scripts/setup.sh" +# fi +# done +# +# Vibetype-specific Node.js setup is intentionally skipped here for now. +# Run it manually if needed: +# +# cd "$TARGET_DIR/vibetype" +# nvm install +# corepack enable +# pnpm install + +# ── build & deploy ──────────────────────────────────────────────────────────── + +info "Step 4/4 — Using dargstack to build development images and to deploy the stack …" +info " 'dargstack build' builds the development Dockerfiles for cloned first-party services." +info " 'dargstack deploy' runs the stack, a set of services, for development." +info " Use --help on any dargstack command to learn more about it." + +cd "$TARGET_DIR/stack" + +# Build all services whose source code lives in a cloned repository. +SERVICES_TO_BUILD=("postgraphile" "sqitch" "vibetype") +$CLONE_RECCOOM && SERVICES_TO_BUILD+=("reccoom") + +info "Building development container images: ${SERVICES_TO_BUILD[*]} …" +for svc in "${SERVICES_TO_BUILD[@]}"; do + dargstack build "$svc" || warn "Build failed for $svc. You can retry with: dargstack build $svc" +done + +success "Development images ready." + +# Build the deploy command with any extra profiles. +DEPLOY_ARGS=() +for p in "${EXTRA_PROFILES[@]}"; do + DEPLOY_ARGS+=("--profiles" "$p") +done + +info "Deploying the development stack …" +dargstack deploy "${DEPLOY_ARGS[@]}" + +success "Setup complete! Vibetype is available at https://app.localhost 🎉" diff --git a/src/development/vibetype/compose.yaml b/src/development/vibetype/compose.yaml index 94c380c0..03649bf0 100644 --- a/src/development/vibetype/compose.yaml +++ b/src/development/vibetype/compose.yaml @@ -66,7 +66,6 @@ services: target: /run/environment-variables/PGPASSWORD - source: postgres-role-service-vibetype-username target: /run/environment-variables/PGUSER - user: node:node # files created inside a docker container, like node_modules by pnpm, gain correct permissions by setting the user to `node` volumes: - pnpm-data:/srv/.pnpm-store/ # dargstack:dev-only - ../../../artifacts/certificates/:/srv/certificates/ # dargstack:dev-only diff --git a/src/development/zammad/configurations/docker-entrypoint.sh b/src/development/zammad/configurations/docker-entrypoint.sh index 1459d9b7..9d364edd 100755 --- a/src/development/zammad/configurations/docker-entrypoint.sh +++ b/src/development/zammad/configurations/docker-entrypoint.sh @@ -7,7 +7,7 @@ ENVIRONMENT_VARIABLES_PATH="/run/environment-variables" is_valid_var_name() { case "$1" in - *[!a-zA-Z0-9_]*|'') return 1 ;; + ''|[!a-zA-Z_]*|*[!a-zA-Z0-9_]*) return 1 ;; *) return 0 ;; esac } @@ -16,7 +16,7 @@ load_env_file() { file="$1" name=$(basename "$file") is_valid_var_name "$name" || return 0 - value="$(cat "$file")" + value=$(cat "$file") export "$name=$value" } diff --git a/src/production/vibetype/compose.yaml b/src/production/vibetype/compose.yaml index 164fe583..ea28950b 100644 --- a/src/production/vibetype/compose.yaml +++ b/src/production/vibetype/compose.yaml @@ -6,7 +6,6 @@ services: - traefik.http.routers.vibetype.middlewares=vibetype_cors,vibetype_redirectregex - traefik.http.routers.vibetype-secure.tls.certresolver=default image: ghcr.io/maevsi/vibetype:13.3.3 - user: (( prune )) # vibetype-beta: # # You can access the main project frontend's beta version at [beta.app.localhost](https://beta.app.localhost/). # deploy: