Add pre-built image installer and release tarball system
New install method: curl one-liner downloads a lightweight release tarball (~9 MB) and runs the config wizard. No git clone needed, no TypeScript compilation — pulls pre-built images from Gitea registry. - docker-compose.prod.yml: production compose without build blocks or source code volume mounts; IMAGE_TAG defaults to latest - scripts/install.sh: curl-friendly installer (downloads tarball, extracts, runs config.sh) - scripts/build-release.sh: creates release tarball from dev repo with only runtime files (configs, scripts, docs, empty data dirs) - config.sh: release-mode detection (VERSION file + no .git dir), auto-sets IMAGE_TAG=latest and NODE_ENV=production - upgrade.sh: release-mode upgrade path (downloads new tarball from Gitea Releases API instead of git pull, always uses registry mode) - upgrade-check.sh: release-mode version check via Gitea API - .gitignore: exclude releases/ and api/dist/ - Docs: updated getting-started with pre-built install instructions Bunker Admin
This commit is contained in:
parent
f550423c3f
commit
8e6f0996de
9
.gitignore
vendored
9
.gitignore
vendored
@ -51,14 +51,21 @@ docker-compose.override.yml
|
|||||||
core.*
|
core.*
|
||||||
*/core.*
|
*/core.*
|
||||||
|
|
||||||
# MkDocs core binary
|
# MkDocs core binary and container-generated assets (owned by root, not stashable)
|
||||||
/mkdocs/core
|
/mkdocs/core
|
||||||
|
/mkdocs/assets/
|
||||||
|
|
||||||
# Upgrade artifacts
|
# Upgrade artifacts
|
||||||
/logs/
|
/logs/
|
||||||
/backups/
|
/backups/
|
||||||
.upgrade.lock
|
.upgrade.lock
|
||||||
|
|
||||||
|
# Release tarballs (generated by build-release.sh)
|
||||||
|
/releases/
|
||||||
|
|
||||||
|
# API compiled output (generated by tsc, baked into Docker images)
|
||||||
|
/api/dist/
|
||||||
|
|
||||||
# Control Panel runtime data (managed deployments + backups)
|
# Control Panel runtime data (managed deployments + backups)
|
||||||
/changemaker-control-panel/instances/
|
/changemaker-control-panel/instances/
|
||||||
/changemaker-control-panel/backups/
|
/changemaker-control-panel/backups/
|
||||||
|
|||||||
94
config.sh
94
config.sh
@ -12,6 +12,17 @@ ENV_EXAMPLE="$SCRIPT_DIR/.env.example"
|
|||||||
MKDOCS_YML="$SCRIPT_DIR/mkdocs/mkdocs.yml"
|
MKDOCS_YML="$SCRIPT_DIR/mkdocs/mkdocs.yml"
|
||||||
SERVICES_YAML="$SCRIPT_DIR/configs/homepage/services.yaml"
|
SERVICES_YAML="$SCRIPT_DIR/configs/homepage/services.yaml"
|
||||||
|
|
||||||
|
# --- Detect install mode ---
|
||||||
|
# Release mode: installed from tarball (has VERSION file, no .git directory)
|
||||||
|
# Source mode: cloned from git repository
|
||||||
|
if [[ -f "$SCRIPT_DIR/VERSION" ]] && [[ ! -d "$SCRIPT_DIR/.git" ]]; then
|
||||||
|
INSTALL_MODE="release"
|
||||||
|
RELEASE_VERSION=$(head -1 "$SCRIPT_DIR/VERSION")
|
||||||
|
else
|
||||||
|
INSTALL_MODE="source"
|
||||||
|
RELEASE_VERSION=""
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Colors (respects NO_COLOR convention) ---
|
# --- Colors (respects NO_COLOR convention) ---
|
||||||
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
|
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
|
||||||
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
|
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
|
||||||
@ -275,17 +286,19 @@ configure_admin() {
|
|||||||
generate_all_secrets() {
|
generate_all_secrets() {
|
||||||
header "Generating Secrets"
|
header "Generating Secrets"
|
||||||
|
|
||||||
info "Auto-generating 21 unique secrets and passwords..."
|
info "Auto-generating 22 unique secrets and passwords..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# JWT & Encryption (64-char hex)
|
# JWT & Encryption (64-char hex)
|
||||||
local jwt_access jwt_refresh enc_key
|
local jwt_access jwt_refresh jwt_invite enc_key
|
||||||
jwt_access=$(generate_secret)
|
jwt_access=$(generate_secret)
|
||||||
jwt_refresh=$(generate_secret)
|
jwt_refresh=$(generate_secret)
|
||||||
|
jwt_invite=$(generate_secret)
|
||||||
enc_key=$(generate_secret)
|
enc_key=$(generate_secret)
|
||||||
|
|
||||||
update_env_var "JWT_ACCESS_SECRET" "$jwt_access"
|
update_env_var "JWT_ACCESS_SECRET" "$jwt_access"
|
||||||
update_env_var "JWT_REFRESH_SECRET" "$jwt_refresh"
|
update_env_var "JWT_REFRESH_SECRET" "$jwt_refresh"
|
||||||
|
update_env_var "JWT_INVITE_SECRET" "$jwt_invite"
|
||||||
update_env_var "ENCRYPTION_KEY" "$enc_key"
|
update_env_var "ENCRYPTION_KEY" "$enc_key"
|
||||||
success "JWT secrets + encryption key"
|
success "JWT secrets + encryption key"
|
||||||
|
|
||||||
@ -1082,7 +1095,7 @@ print_summary() {
|
|||||||
echo -e " ${BOLD}Bunker Ops:${NC} ${BUNKER_OPS_ENABLED:-no}"
|
echo -e " ${BOLD}Bunker Ops:${NC} ${BUNKER_OPS_ENABLED:-no}"
|
||||||
echo -e " ${BOLD}Pangolin:${NC} ${PANGOLIN_CONFIGURED:-no}"
|
echo -e " ${BOLD}Pangolin:${NC} ${PANGOLIN_CONFIGURED:-no}"
|
||||||
echo -e " ${BOLD}Upgrade watcher:${NC} ${UPGRADE_WATCHER:-skipped}"
|
echo -e " ${BOLD}Upgrade watcher:${NC} ${UPGRADE_WATCHER:-skipped}"
|
||||||
echo -e " ${BOLD}Secrets:${NC} 21 auto-generated"
|
echo -e " ${BOLD}Secrets:${NC} 22 auto-generated"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${DIM}Config file: $ENV_FILE${NC}"
|
echo -e " ${DIM}Config file: $ENV_FILE${NC}"
|
||||||
}
|
}
|
||||||
@ -1093,29 +1106,50 @@ print_next_steps() {
|
|||||||
echo -e "${BOLD}${BLUE} Next Steps${NC}"
|
echo -e "${BOLD}${BLUE} Next Steps${NC}"
|
||||||
echo -e "${BOLD}${BLUE}══════════════════════════════════════${NC}"
|
echo -e "${BOLD}${BLUE}══════════════════════════════════════${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${BOLD}1.${NC} Start core services:"
|
|
||||||
echo -e " ${CYAN}docker compose up -d v2-postgres redis api admin${NC}"
|
if [[ "$INSTALL_MODE" == "release" ]]; then
|
||||||
echo ""
|
# Release mode: simpler instructions (production images, auto-migration)
|
||||||
echo -e " ${BOLD}2.${NC} Run database setup:"
|
echo -e " ${BOLD}1.${NC} Start all services:"
|
||||||
echo -e " ${CYAN}docker compose exec api npx prisma migrate deploy${NC}"
|
echo -e " ${CYAN}docker compose up -d${NC}"
|
||||||
echo -e " ${CYAN}docker compose exec api npx prisma db seed${NC}"
|
echo ""
|
||||||
echo ""
|
echo -e " Pre-built images will be pulled from the registry (~2 min first time)."
|
||||||
echo -e " ${BOLD}3.${NC} Access the application:"
|
echo -e " Database migrations and seeding run automatically on startup."
|
||||||
echo -e " Admin GUI: ${CYAN}http://localhost:3000${NC}"
|
echo ""
|
||||||
echo -e " API: ${CYAN}http://localhost:4000${NC}"
|
echo -e " ${BOLD}2.${NC} Access the application:"
|
||||||
echo ""
|
echo -e " Admin GUI: ${CYAN}http://localhost:3000${NC}"
|
||||||
echo -e " ${BOLD}4.${NC} Optional — start additional services:"
|
echo -e " API: ${CYAN}http://localhost:4000${NC}"
|
||||||
echo -e " ${CYAN}docker compose up -d nginx${NC} # Reverse proxy"
|
echo ""
|
||||||
echo -e " ${CYAN}docker compose up -d media-api${NC} # Video library"
|
echo -e " ${BOLD}3.${NC} Check status:"
|
||||||
echo -e " ${CYAN}docker compose up -d listmonk-app${NC} # Newsletters"
|
echo -e " ${CYAN}docker compose ps${NC}"
|
||||||
echo -e " ${CYAN}docker compose up -d rocketchat${NC} # Team chat"
|
echo -e " ${CYAN}docker compose logs -f api --tail 20${NC}"
|
||||||
echo -e " ${CYAN}docker compose up -d jitsi-web jitsi-prosody jitsi-jicofo jitsi-jvb${NC} # Video calls"
|
echo ""
|
||||||
echo -e " ${CYAN}docker compose up -d homepage${NC} # Service dashboard"
|
else
|
||||||
echo -e " ${CYAN}docker compose --profile monitoring up -d${NC} # Monitoring"
|
# Source mode: existing instructions
|
||||||
echo ""
|
echo -e " ${BOLD}1.${NC} Start core services:"
|
||||||
echo -e " ${BOLD}5.${NC} Or start everything at once:"
|
echo -e " ${CYAN}docker compose up -d v2-postgres redis api admin${NC}"
|
||||||
echo -e " ${CYAN}docker compose up -d${NC}"
|
echo ""
|
||||||
echo ""
|
echo -e " ${BOLD}2.${NC} Run database setup:"
|
||||||
|
echo -e " ${CYAN}docker compose exec api npx prisma migrate deploy${NC}"
|
||||||
|
echo -e " ${CYAN}docker compose exec api npx prisma db seed${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}3.${NC} Access the application:"
|
||||||
|
echo -e " Admin GUI: ${CYAN}http://localhost:3000${NC}"
|
||||||
|
echo -e " API: ${CYAN}http://localhost:4000${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}4.${NC} Optional — start additional services:"
|
||||||
|
echo -e " ${CYAN}docker compose up -d nginx${NC} # Reverse proxy"
|
||||||
|
echo -e " ${CYAN}docker compose up -d media-api${NC} # Video library"
|
||||||
|
echo -e " ${CYAN}docker compose up -d listmonk-app${NC} # Newsletters"
|
||||||
|
echo -e " ${CYAN}docker compose up -d rocketchat${NC} # Team chat"
|
||||||
|
echo -e " ${CYAN}docker compose up -d jitsi-web jitsi-prosody jitsi-jicofo jitsi-jvb${NC} # Video calls"
|
||||||
|
echo -e " ${CYAN}docker compose up -d homepage${NC} # Service dashboard"
|
||||||
|
echo -e " ${CYAN}docker compose --profile monitoring up -d${NC} # Monitoring"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}5.${NC} Or start everything at once:"
|
||||||
|
echo -e " ${CYAN}docker compose up -d${NC}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e " ${YELLOW}IMPORTANT: Change your admin password after first login!${NC}"
|
echo -e " ${YELLOW}IMPORTANT: Change your admin password after first login!${NC}"
|
||||||
echo -e " ${YELLOW}JITSI: Ensure UDP port 10000 is open in your firewall for video/audio.${NC}"
|
echo -e " ${YELLOW}JITSI: Ensure UDP port 10000 is open in your firewall for video/audio.${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
@ -1142,6 +1176,14 @@ main() {
|
|||||||
fix_container_permissions
|
fix_container_permissions
|
||||||
install_upgrade_watcher
|
install_upgrade_watcher
|
||||||
|
|
||||||
|
# Release mode: auto-set production defaults
|
||||||
|
if [[ "$INSTALL_MODE" == "release" ]]; then
|
||||||
|
header "Release Mode Settings"
|
||||||
|
update_env_var "IMAGE_TAG" "latest"
|
||||||
|
update_env_var "NODE_ENV" "production"
|
||||||
|
success "Set IMAGE_TAG=latest, NODE_ENV=production (pre-built images)"
|
||||||
|
fi
|
||||||
|
|
||||||
print_summary
|
print_summary
|
||||||
print_next_steps
|
print_next_steps
|
||||||
}
|
}
|
||||||
|
|||||||
1264
docker-compose.prod.yml
Normal file
1264
docker-compose.prod.yml
Normal file
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,19 @@ This guide walks you through installing Changemaker Lite, running your first dep
|
|||||||
- At least 2 GB RAM and 10 GB disk space
|
- At least 2 GB RAM and 10 GB disk space
|
||||||
- A domain name (optional, but recommended for production)
|
- A domain name (optional, but recommended for production)
|
||||||
|
|
||||||
## Quick Start
|
## Quick Install (Pre-built Images)
|
||||||
|
|
||||||
|
The fastest way to deploy — no source code, no compilation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://gitea.bnkops.com/admin/changemaker.lite/raw/branch/v2/scripts/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
This downloads a lightweight release package (~2 MB), runs the configuration wizard, and pulls pre-built Docker images. First startup takes ~2 minutes. See [Installation](installation.md#pre-built-image-installation) for details.
|
||||||
|
|
||||||
|
## Quick Start (From Source)
|
||||||
|
|
||||||
|
For development or customization, clone the full repository:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://gitea.bnkops.com/admin/changemaker.lite
|
git clone https://gitea.bnkops.com/admin/changemaker.lite
|
||||||
|
|||||||
@ -49,6 +49,54 @@ Open **http://localhost:3000** and sign in with the admin credentials you config
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Pre-built Image Installation
|
||||||
|
|
||||||
|
For production deployments, you can skip cloning the source repository entirely. Pre-built Docker images are pulled from the Gitea container registry.
|
||||||
|
|
||||||
|
### One-Line Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://gitea.bnkops.com/admin/changemaker.lite/raw/branch/v2/scripts/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
This script:
|
||||||
|
|
||||||
|
1. Checks prerequisites (Docker, Docker Compose, OpenSSL)
|
||||||
|
2. Downloads the latest release package from Gitea
|
||||||
|
3. Extracts to `~/changemaker.lite/`
|
||||||
|
4. Launches the configuration wizard (`config.sh`)
|
||||||
|
|
||||||
|
After the wizard completes, start everything with `docker compose up -d`.
|
||||||
|
|
||||||
|
### Manual Download
|
||||||
|
|
||||||
|
If you prefer not to pipe to bash:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Download latest release
|
||||||
|
curl -LO https://gitea.bnkops.com/admin/changemaker.lite/releases/latest/download/changemaker-lite-latest.tar.gz
|
||||||
|
tar xzf changemaker-lite-latest.tar.gz
|
||||||
|
cd changemaker-lite
|
||||||
|
bash config.sh
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### What's Different from Source Install
|
||||||
|
|
||||||
|
| | Source Install | Pre-built Install |
|
||||||
|
|---|---|---|
|
||||||
|
| **Download size** | ~200 MB (full repo) | ~2 MB (config + scripts) |
|
||||||
|
| **First startup** | 10+ min (TypeScript compile + Docker build) | ~2 min (image pull only) |
|
||||||
|
| **Requires** | Git, full repo | Docker only |
|
||||||
|
| **Upgrades** | `git pull` + rebuild | Download new release tarball |
|
||||||
|
| **Development** | Edit source, hot-reload | Not for development |
|
||||||
|
|
||||||
|
!!! tip "When to use which"
|
||||||
|
Use **pre-built install** for production deployments and quick evaluation.
|
||||||
|
Use **source install** when you want to modify the platform code or contribute to development.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Configuration Wizard (`config.sh`)
|
## Configuration Wizard (`config.sh`)
|
||||||
|
|
||||||
The wizard performs **14 steps** to produce a fully configured `.env` file and prepare the system for startup. Each step is interactive with sensible defaults.
|
The wizard performs **14 steps** to produce a fully configured `.env` file and prepare the system for startup. Each step is interactive with sensible defaults.
|
||||||
@ -101,7 +149,7 @@ Auto-generates **21 unique secrets** — no placeholder passwords remain after t
|
|||||||
|
|
||||||
| Category | Count | Secrets |
|
| Category | Count | Secrets |
|
||||||
|----------|-------|---------|
|
|----------|-------|---------|
|
||||||
| JWT & Encryption | 3 | `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET`, `ENCRYPTION_KEY` (64-char hex) |
|
| JWT & Encryption | 4 | `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET`, `JWT_INVITE_SECRET` (each 64-char hex), `ENCRYPTION_KEY` (64-char hex, must differ from JWT secrets) |
|
||||||
| Database | 2 | `V2_POSTGRES_PASSWORD`, `REDIS_PASSWORD` (24-char alphanumeric) |
|
| Database | 2 | `V2_POSTGRES_PASSWORD`, `REDIS_PASSWORD` (24-char alphanumeric) |
|
||||||
| Listmonk | 3 | `LISTMONK_DB_PASSWORD`, `LISTMONK_WEB_ADMIN_PASSWORD`, `LISTMONK_API_TOKEN` |
|
| Listmonk | 3 | `LISTMONK_DB_PASSWORD`, `LISTMONK_WEB_ADMIN_PASSWORD`, `LISTMONK_API_TOKEN` |
|
||||||
| NocoDB | 1 | `NC_ADMIN_PASSWORD` |
|
| NocoDB | 1 | `NC_ADMIN_PASSWORD` |
|
||||||
@ -227,7 +275,8 @@ V2_POSTGRES_PASSWORD=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 24
|
|||||||
REDIS_PASSWORD=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 24)
|
REDIS_PASSWORD=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 24)
|
||||||
JWT_ACCESS_SECRET=$(openssl rand -hex 32)
|
JWT_ACCESS_SECRET=$(openssl rand -hex 32)
|
||||||
JWT_REFRESH_SECRET=$(openssl rand -hex 32)
|
JWT_REFRESH_SECRET=$(openssl rand -hex 32)
|
||||||
ENCRYPTION_KEY=$(openssl rand -hex 32)
|
JWT_INVITE_SECRET=$(openssl rand -hex 32)
|
||||||
|
ENCRYPTION_KEY=$(openssl rand -hex 32) # must differ from all JWT secrets
|
||||||
```
|
```
|
||||||
|
|
||||||
Set your admin credentials (password must meet the 12+ char complexity requirement):
|
Set your admin credentials (password must meet the 12+ char complexity requirement):
|
||||||
|
|||||||
@ -71,6 +71,7 @@ The system fetches from the git remote and shows:
|
|||||||
3. Optionally configure:
|
3. Optionally configure:
|
||||||
- **Skip backup** — skip the database backup phase (not recommended)
|
- **Skip backup** — skip the database backup phase (not recommended)
|
||||||
- **Pull images** — also update third-party Docker images (PostgreSQL, Redis, etc.)
|
- **Pull images** — also update third-party Docker images (PostgreSQL, Redis, etc.)
|
||||||
|
- **Use registry images** — pull pre-built images from Gitea instead of compiling from source (faster — requires `scripts/build-and-push.sh` to have been run first)
|
||||||
- **Dry run** — preview what would happen without making changes
|
- **Dry run** — preview what would happen without making changes
|
||||||
4. Monitor the 6-phase progress indicator
|
4. Monitor the 6-phase progress indicator
|
||||||
|
|
||||||
@ -87,7 +88,7 @@ Both the GUI and CLI methods execute the same 6-phase process:
|
|||||||
| **1** | 5% | Pre-flight Checks | Verifies Docker, git, disk space (2 GB minimum), remote reachability, and clean working directory |
|
| **1** | 5% | Pre-flight Checks | Verifies Docker, git, disk space (2 GB minimum), remote reachability, and clean working directory |
|
||||||
| **2** | 15% | Backup | Runs `scripts/backup.sh` (pg_dump + archive), backs up user-modifiable content, saves pre-upgrade commit hash |
|
| **2** | 15% | Backup | Runs `scripts/backup.sh` (pg_dump + archive), backs up user-modifiable content, saves pre-upgrade commit hash |
|
||||||
| **3** | 30% | Code Update | Saves user paths, stashes local changes, `git pull`, pops stash with auto-conflict resolution, detects new `.env` variables |
|
| **3** | 30% | Code Update | Saves user paths, stashes local changes, `git pull`, pops stash with auto-conflict resolution, detects new `.env` variables |
|
||||||
| **4** | 50% | Container Rebuild | Rebuilds `api`, `admin`, `media-api`; conditionally rebuilds `nginx` and `code-server` if their configs changed; optionally pulls third-party images |
|
| **4** | 50% | Container Rebuild | Rebuilds `api`, `admin`, `media-api` from source (default) **or** pulls pre-built images from the Gitea registry (`--use-registry`); conditionally rebuilds `nginx` and `code-server` if their configs changed; optionally pulls third-party images |
|
||||||
| **5** | 70% | Service Restart | Stops app containers, force-recreates LSIO containers, verifies Gancio config, starts infrastructure, waits for PostgreSQL, starts API (runs migrations), starts everything else, restarts Newt tunnel and monitoring if they were running |
|
| **5** | 70% | Service Restart | Stops app containers, force-recreates LSIO containers, verifies Gancio config, starts infrastructure, waits for PostgreSQL, starts API (runs migrations), starts everything else, restarts Newt tunnel and monitoring if they were running |
|
||||||
| **6** | 90% | Verification | Health checks for API, Admin, Media API, Gancio, MkDocs; detects containers in restart loops |
|
| **6** | 90% | Verification | Health checks for API, Admin, Media API, Gancio, MkDocs; detects containers in restart loops |
|
||||||
|
|
||||||
@ -126,6 +127,7 @@ Run the upgrade script directly:
|
|||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `--skip-backup` | Skip the backup phase (requires `--force`) |
|
| `--skip-backup` | Skip the backup phase (requires `--force`) |
|
||||||
| `--pull-services` | Also pull new third-party Docker images |
|
| `--pull-services` | Also pull new third-party Docker images |
|
||||||
|
| `--use-registry` | Pull pre-built images from Gitea instead of compiling from source |
|
||||||
| `--dry-run` | Show what would happen without executing |
|
| `--dry-run` | Show what would happen without executing |
|
||||||
| `--force` | Continue past non-critical warnings |
|
| `--force` | Continue past non-critical warnings |
|
||||||
| `--branch BRANCH` | Git branch to pull (default: current branch) |
|
| `--branch BRANCH` | Git branch to pull (default: current branch) |
|
||||||
@ -144,12 +146,52 @@ Run the upgrade script directly:
|
|||||||
# Full upgrade including third-party image updates
|
# Full upgrade including third-party image updates
|
||||||
./scripts/upgrade.sh --pull-services
|
./scripts/upgrade.sh --pull-services
|
||||||
|
|
||||||
|
# Upgrade using pre-built images from Gitea registry (faster, no TypeScript compile)
|
||||||
|
./scripts/upgrade.sh --use-registry --force --skip-backup
|
||||||
|
|
||||||
# Rollback to the last pre-upgrade state
|
# Rollback to the last pre-upgrade state
|
||||||
./scripts/upgrade.sh --rollback
|
./scripts/upgrade.sh --rollback
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Registry Mode (Fast Upgrades)
|
||||||
|
|
||||||
|
By default, the upgrade script compiles TypeScript from source (`npm run build`) and rebuilds Docker images on the deployment server. **Registry mode** skips this by pulling pre-built production images from the Gitea container registry — faster and requires no build tooling on the server.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. Run `scripts/build-and-push.sh` on a machine with Docker (usually your dev machine) to build and push production images tagged with the current commit SHA
|
||||||
|
2. During the next upgrade, pass `--use-registry` (CLI) or enable the checkbox (GUI)
|
||||||
|
3. The upgrade script pulls `gitea.bnkops.com/admin/changemaker-{service}:{sha}` instead of rebuilding from source
|
||||||
|
4. If a registry image is unavailable (e.g., the SHA wasn't pushed), it automatically falls back to a source build
|
||||||
|
|
||||||
|
### Building and Pushing Images
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and push all core services (api, admin, media-api, nginx)
|
||||||
|
./scripts/build-and-push.sh
|
||||||
|
|
||||||
|
# Skip code-server (9 GB — push only when Dockerfile changes)
|
||||||
|
./scripts/build-and-push.sh --services api,admin,media-api,nginx
|
||||||
|
|
||||||
|
# Build only, no push (verify locally first)
|
||||||
|
./scripts/build-and-push.sh --no-push
|
||||||
|
|
||||||
|
# Also mirror third-party images (postgres, redis, etc.) to Gitea
|
||||||
|
./scripts/mirror-images.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note "Registry prerequisites"
|
||||||
|
- Run `docker login gitea.bnkops.com` once per machine before pushing
|
||||||
|
- Set `GITEA_REGISTRY_USER` and `GITEA_REGISTRY_PASS` in `.env` for the admin GUI's Registry status endpoint
|
||||||
|
- gitea.bnkops.com must be reachable without proxies that limit upload size (Cloudflare free plan blocks blobs >100 MB)
|
||||||
|
|
||||||
|
!!! info "Release installs upgrade automatically via registry"
|
||||||
|
If you installed from a release tarball (not git clone), the upgrade script automatically uses registry mode. It downloads the latest release package from Gitea instead of running `git pull`. No additional configuration needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Rollback
|
## Rollback
|
||||||
|
|
||||||
### Automatic Rollback
|
### Automatic Rollback
|
||||||
|
|||||||
235
scripts/build-release.sh
Executable file
235
scripts/build-release.sh
Executable file
@ -0,0 +1,235 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================================
|
||||||
|
# Changemaker Lite V2 — Build Release Tarball
|
||||||
|
#
|
||||||
|
# Creates a lightweight release tarball (~1-2 MB) containing only runtime files
|
||||||
|
# needed to deploy with pre-built Docker images. No source code included.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/build-release.sh [OPTIONS]
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --tag TAG Version tag (default: git describe or commit SHA)
|
||||||
|
# --output DIR Output directory (default: ./releases/)
|
||||||
|
# --upload Upload to Gitea Releases API after building
|
||||||
|
# --dry-run Show what would be included without creating tarball
|
||||||
|
# --help Show this help
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# Run ./scripts/build-and-push.sh first to push Docker images to registry.
|
||||||
|
# =============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
|
# --- Defaults ---
|
||||||
|
TAG=""
|
||||||
|
OUTPUT_DIR="${PROJECT_DIR}/releases"
|
||||||
|
UPLOAD=false
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
# --- Colors ---
|
||||||
|
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
|
||||||
|
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
|
||||||
|
BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m'
|
||||||
|
else
|
||||||
|
RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' NC=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||||
|
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||||
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||||
|
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||||
|
|
||||||
|
# --- Arg parser ---
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--tag) TAG="$2"; shift 2 ;;
|
||||||
|
--output) OUTPUT_DIR="$2"; shift 2 ;;
|
||||||
|
--upload) UPLOAD=true; shift ;;
|
||||||
|
--dry-run) DRY_RUN=true; shift ;;
|
||||||
|
--help|-h)
|
||||||
|
sed -n '2,20p' "$0" | grep '^#' | sed 's/^# \?//'
|
||||||
|
exit 0 ;;
|
||||||
|
*) error "Unknown option: $1"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Determine version ---
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
if [[ -z "$TAG" ]]; then
|
||||||
|
TAG="$(git describe --tags --always 2>/dev/null || git rev-parse --short HEAD)"
|
||||||
|
fi
|
||||||
|
COMMIT_SHA="$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")"
|
||||||
|
BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
|
||||||
|
echo -e "${BOLD}Changemaker Lite — Build Release${NC}"
|
||||||
|
echo " Tag: $TAG"
|
||||||
|
echo " Commit: $COMMIT_SHA"
|
||||||
|
echo " Date: $BUILD_DATE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Create staging directory ---
|
||||||
|
STAGE_DIR="$(mktemp -d)/changemaker-lite"
|
||||||
|
mkdir -p "$STAGE_DIR"
|
||||||
|
|
||||||
|
# --- Write VERSION file ---
|
||||||
|
cat > "$STAGE_DIR/VERSION" << EOF
|
||||||
|
$TAG
|
||||||
|
$COMMIT_SHA
|
||||||
|
$BUILD_DATE
|
||||||
|
EOF
|
||||||
|
info "VERSION: $TAG ($COMMIT_SHA)"
|
||||||
|
|
||||||
|
# --- Copy production docker-compose ---
|
||||||
|
if [[ ! -f "$PROJECT_DIR/docker-compose.prod.yml" ]]; then
|
||||||
|
error "docker-compose.prod.yml not found. Generate it first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cp "$PROJECT_DIR/docker-compose.prod.yml" "$STAGE_DIR/docker-compose.yml"
|
||||||
|
info "docker-compose.yml (production)"
|
||||||
|
|
||||||
|
# --- Copy config files ---
|
||||||
|
cp "$PROJECT_DIR/.env.example" "$STAGE_DIR/"
|
||||||
|
cp "$PROJECT_DIR/config.sh" "$STAGE_DIR/"
|
||||||
|
info "Config files (.env.example, config.sh)"
|
||||||
|
|
||||||
|
# --- Copy scripts ---
|
||||||
|
mkdir -p "$STAGE_DIR/scripts"
|
||||||
|
|
||||||
|
# Init scripts (from api/prisma/ to scripts/)
|
||||||
|
cp "$PROJECT_DIR/api/prisma/init-nocodb-db.sh" "$STAGE_DIR/scripts/"
|
||||||
|
cp "$PROJECT_DIR/api/prisma/init-gancio-db.sh" "$STAGE_DIR/scripts/"
|
||||||
|
|
||||||
|
# Runtime scripts
|
||||||
|
for script in nocodb-init.sh mkdocs-entrypoint.sh backup.sh \
|
||||||
|
upgrade.sh upgrade-check.sh upgrade-watcher.sh; do
|
||||||
|
if [[ -f "$PROJECT_DIR/scripts/$script" ]]; then
|
||||||
|
cp "$PROJECT_DIR/scripts/$script" "$STAGE_DIR/scripts/"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# MkDocs build trigger
|
||||||
|
if [[ -f "$PROJECT_DIR/scripts/mkdocs-build-trigger.py" ]]; then
|
||||||
|
cp "$PROJECT_DIR/scripts/mkdocs-build-trigger.py" "$STAGE_DIR/scripts/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Systemd units
|
||||||
|
if [[ -d "$PROJECT_DIR/scripts/systemd" ]]; then
|
||||||
|
cp -r "$PROJECT_DIR/scripts/systemd" "$STAGE_DIR/scripts/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install script (for reference)
|
||||||
|
cp "$PROJECT_DIR/scripts/install.sh" "$STAGE_DIR/scripts/"
|
||||||
|
|
||||||
|
chmod +x "$STAGE_DIR/scripts/"*.sh 2>/dev/null || true
|
||||||
|
info "Scripts ($(ls "$STAGE_DIR/scripts/" | wc -l) files)"
|
||||||
|
|
||||||
|
# --- Copy configs ---
|
||||||
|
if [[ -d "$PROJECT_DIR/configs" ]]; then
|
||||||
|
cp -r "$PROJECT_DIR/configs" "$STAGE_DIR/"
|
||||||
|
# Ensure code-server skeleton dirs exist
|
||||||
|
mkdir -p "$STAGE_DIR/configs/code-server/.config"
|
||||||
|
mkdir -p "$STAGE_DIR/configs/code-server/.local"
|
||||||
|
info "Configs (homepage, prometheus, grafana, alertmanager, pangolin)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Copy nginx templates (for reference) ---
|
||||||
|
mkdir -p "$STAGE_DIR/nginx/conf.d"
|
||||||
|
cp "$PROJECT_DIR"/nginx/conf.d/*.template "$STAGE_DIR/nginx/conf.d/" 2>/dev/null || true
|
||||||
|
info "Nginx templates (for reference)"
|
||||||
|
|
||||||
|
# --- Copy MkDocs starter docs ---
|
||||||
|
if [[ -d "$PROJECT_DIR/mkdocs" ]]; then
|
||||||
|
mkdir -p "$STAGE_DIR/mkdocs"
|
||||||
|
cp "$PROJECT_DIR/mkdocs/mkdocs.yml" "$STAGE_DIR/mkdocs/" 2>/dev/null || true
|
||||||
|
cp -r "$PROJECT_DIR/mkdocs/docs" "$STAGE_DIR/mkdocs/" 2>/dev/null || true
|
||||||
|
cp -r "$PROJECT_DIR/mkdocs/overrides" "$STAGE_DIR/mkdocs/" 2>/dev/null || true
|
||||||
|
mkdir -p "$STAGE_DIR/mkdocs/.cache"
|
||||||
|
mkdir -p "$STAGE_DIR/mkdocs/site"
|
||||||
|
info "MkDocs (starter documentation)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Copy public-web ---
|
||||||
|
if [[ -d "$PROJECT_DIR/public-web" ]]; then
|
||||||
|
cp -r "$PROJECT_DIR/public-web" "$STAGE_DIR/"
|
||||||
|
else
|
||||||
|
mkdir -p "$STAGE_DIR/public-web"
|
||||||
|
fi
|
||||||
|
info "Public web assets"
|
||||||
|
|
||||||
|
# --- Create empty data directories ---
|
||||||
|
mkdir -p "$STAGE_DIR/assets/uploads"
|
||||||
|
mkdir -p "$STAGE_DIR/assets/icons"
|
||||||
|
mkdir -p "$STAGE_DIR/assets/images"
|
||||||
|
mkdir -p "$STAGE_DIR/data/upgrade"
|
||||||
|
mkdir -p "$STAGE_DIR/media/local/inbox"
|
||||||
|
mkdir -p "$STAGE_DIR/media/local/thumbnails"
|
||||||
|
mkdir -p "$STAGE_DIR/media/local/photos"
|
||||||
|
mkdir -p "$STAGE_DIR/media/public"
|
||||||
|
mkdir -p "$STAGE_DIR/local-files"
|
||||||
|
info "Data directories (empty)"
|
||||||
|
|
||||||
|
# --- Summary ---
|
||||||
|
echo ""
|
||||||
|
STAGE_SIZE=$(du -sh "$STAGE_DIR" | cut -f1)
|
||||||
|
FILE_COUNT=$(find "$STAGE_DIR" -type f | wc -l)
|
||||||
|
info "Staging: ${FILE_COUNT} files, ${STAGE_SIZE}"
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
echo ""
|
||||||
|
info "[DRY RUN] Would create: changemaker-lite-${TAG}.tar.gz"
|
||||||
|
find "$STAGE_DIR" -type f | sed "s|$STAGE_DIR/||" | sort
|
||||||
|
rm -rf "$(dirname "$STAGE_DIR")"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Create tarball ---
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
TARBALL="${OUTPUT_DIR}/changemaker-lite-${TAG}.tar.gz"
|
||||||
|
|
||||||
|
tar czf "$TARBALL" -C "$(dirname "$STAGE_DIR")" "changemaker-lite"
|
||||||
|
rm -rf "$(dirname "$STAGE_DIR")"
|
||||||
|
|
||||||
|
TARBALL_SIZE=$(du -h "$TARBALL" | cut -f1)
|
||||||
|
success "Created: $TARBALL (${TARBALL_SIZE})"
|
||||||
|
|
||||||
|
# --- Upload to Gitea (optional) ---
|
||||||
|
if [[ "$UPLOAD" == "true" ]]; then
|
||||||
|
source "$PROJECT_DIR/.env" 2>/dev/null || true
|
||||||
|
GITEA_TOKEN="${GITEA_API_TOKEN:-}"
|
||||||
|
GITEA_HOST="${GITEA_URL:-https://gitea.bnkops.com}"
|
||||||
|
|
||||||
|
if [[ -z "$GITEA_TOKEN" ]]; then
|
||||||
|
warn "GITEA_API_TOKEN not set — skipping upload"
|
||||||
|
warn "Set GITEA_API_TOKEN in .env and re-run with --upload"
|
||||||
|
else
|
||||||
|
info "Creating Gitea release ${TAG}..."
|
||||||
|
RELEASE_RESPONSE=$(curl -sf -X POST \
|
||||||
|
"${GITEA_HOST}/api/v1/repos/admin/changemaker.lite/releases" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\":\"${TAG}\",\"name\":\"Changemaker Lite ${TAG}\",\"body\":\"Release ${TAG} (${COMMIT_SHA})\"}" \
|
||||||
|
2>/dev/null || true)
|
||||||
|
|
||||||
|
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ -n "$RELEASE_ID" ]]; then
|
||||||
|
info "Uploading tarball to release ${RELEASE_ID}..."
|
||||||
|
curl -sf -X POST \
|
||||||
|
"${GITEA_HOST}/api/v1/repos/admin/changemaker.lite/releases/${RELEASE_ID}/assets" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-F "attachment=@${TARBALL}" \
|
||||||
|
>/dev/null 2>&1
|
||||||
|
success "Uploaded to Gitea release ${TAG}"
|
||||||
|
else
|
||||||
|
warn "Failed to create release — upload manually at ${GITEA_HOST}/admin/changemaker.lite/releases"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
success "Release ${TAG} ready."
|
||||||
|
echo " Tarball: $TARBALL"
|
||||||
|
echo " Install: tar xzf $(basename "$TARBALL") && cd changemaker-lite && bash config.sh"
|
||||||
177
scripts/install.sh
Executable file
177
scripts/install.sh
Executable file
@ -0,0 +1,177 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================================
|
||||||
|
# Changemaker Lite — One-Line Installer
|
||||||
|
#
|
||||||
|
# Downloads the latest release tarball and runs the configuration wizard.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# curl -fsSL https://gitea.bnkops.com/admin/changemaker.lite/raw/branch/v2/scripts/install.sh | bash
|
||||||
|
# bash install.sh [OPTIONS]
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --dir DIR Install directory (default: ~/changemaker.lite)
|
||||||
|
# --version TAG Specific version tag (default: latest release)
|
||||||
|
# --tarball FILE Use a local tarball instead of downloading
|
||||||
|
# --help Show this help
|
||||||
|
# =============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GITEA_URL="https://gitea.bnkops.com"
|
||||||
|
REPO="admin/changemaker.lite"
|
||||||
|
INSTALL_DIR="${HOME}/changemaker.lite"
|
||||||
|
VERSION=""
|
||||||
|
LOCAL_TARBALL=""
|
||||||
|
|
||||||
|
# --- Colors ---
|
||||||
|
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
|
||||||
|
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
|
||||||
|
BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m'
|
||||||
|
else
|
||||||
|
RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||||
|
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||||
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||||
|
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||||
|
|
||||||
|
# --- Arg parser ---
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--dir) INSTALL_DIR="$2"; shift 2 ;;
|
||||||
|
--version) VERSION="$2"; shift 2 ;;
|
||||||
|
--tarball) LOCAL_TARBALL="$2"; shift 2 ;;
|
||||||
|
--help|-h)
|
||||||
|
sed -n '2,20p' "$0" | grep '^#' | sed 's/^# \?//'
|
||||||
|
exit 0 ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "${BOLD}Changemaker Lite — Installer${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Step 1: Check prerequisites ---
|
||||||
|
info "Checking prerequisites..."
|
||||||
|
MISSING=()
|
||||||
|
command -v docker >/dev/null 2>&1 || MISSING+=("docker")
|
||||||
|
docker compose version >/dev/null 2>&1 || MISSING+=("docker-compose-v2")
|
||||||
|
command -v openssl >/dev/null 2>&1 || MISSING+=("openssl")
|
||||||
|
command -v curl >/dev/null 2>&1 || MISSING+=("curl")
|
||||||
|
|
||||||
|
if [[ ${#MISSING[@]} -gt 0 ]]; then
|
||||||
|
error "Missing required tools: ${MISSING[*]}"
|
||||||
|
echo ""
|
||||||
|
echo "Install Docker: https://docs.docker.com/engine/install/"
|
||||||
|
echo "Install OpenSSL: apt install openssl (or equivalent)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
success "Prerequisites OK (Docker $(docker --version | grep -oP '\d+\.\d+\.\d+'), OpenSSL available)"
|
||||||
|
|
||||||
|
# --- Step 2: Check install directory ---
|
||||||
|
if [[ -d "$INSTALL_DIR" ]]; then
|
||||||
|
if [[ -f "$INSTALL_DIR/docker-compose.yml" ]]; then
|
||||||
|
error "Changemaker Lite is already installed at $INSTALL_DIR"
|
||||||
|
echo " To upgrade: cd $INSTALL_DIR && ./scripts/upgrade.sh"
|
||||||
|
echo " To reinstall: rm -rf $INSTALL_DIR && re-run this script"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Step 3: Get tarball ---
|
||||||
|
TARBALL_PATH=""
|
||||||
|
if [[ -n "$LOCAL_TARBALL" ]]; then
|
||||||
|
if [[ ! -f "$LOCAL_TARBALL" ]]; then
|
||||||
|
error "Tarball not found: $LOCAL_TARBALL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
TARBALL_PATH="$LOCAL_TARBALL"
|
||||||
|
info "Using local tarball: $LOCAL_TARBALL"
|
||||||
|
else
|
||||||
|
# Determine download URL
|
||||||
|
if [[ -n "$VERSION" ]]; then
|
||||||
|
RELEASE_URL="${GITEA_URL}/api/v1/repos/${REPO}/releases/tags/${VERSION}"
|
||||||
|
else
|
||||||
|
RELEASE_URL="${GITEA_URL}/api/v1/repos/${REPO}/releases/latest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Fetching release info from Gitea..."
|
||||||
|
RELEASE_JSON=$(curl -sf "$RELEASE_URL" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ -z "$RELEASE_JSON" ]]; then
|
||||||
|
error "Could not fetch release info from ${GITEA_URL}"
|
||||||
|
echo ""
|
||||||
|
echo "If the registry requires authentication:"
|
||||||
|
echo " 1. Download the tarball manually from ${GITEA_URL}/${REPO}/releases"
|
||||||
|
echo " 2. Run: bash install.sh --tarball /path/to/changemaker-lite-*.tar.gz"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARBALL_URL=$(echo "$RELEASE_JSON" | python3 -c "
|
||||||
|
import sys, json
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
assets = data.get('assets', [])
|
||||||
|
for a in assets:
|
||||||
|
if a['name'].endswith('.tar.gz'):
|
||||||
|
print(a['browser_download_url'])
|
||||||
|
break
|
||||||
|
" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ -z "$TARBALL_URL" ]]; then
|
||||||
|
error "No tarball found in the release. Check ${GITEA_URL}/${REPO}/releases"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
RELEASE_TAG=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tag_name','unknown'))" 2>/dev/null)
|
||||||
|
info "Downloading Changemaker Lite ${RELEASE_TAG}..."
|
||||||
|
TARBALL_PATH="/tmp/changemaker-lite-install.tar.gz"
|
||||||
|
curl -fSL "$TARBALL_URL" -o "$TARBALL_PATH"
|
||||||
|
success "Downloaded $(du -h "$TARBALL_PATH" | cut -f1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Step 4: Extract ---
|
||||||
|
info "Extracting to ${INSTALL_DIR}..."
|
||||||
|
mkdir -p "$(dirname "$INSTALL_DIR")"
|
||||||
|
|
||||||
|
# Extract to temp, then move (handles tarball root directory naming)
|
||||||
|
EXTRACT_DIR=$(mktemp -d)
|
||||||
|
tar xzf "$TARBALL_PATH" -C "$EXTRACT_DIR"
|
||||||
|
|
||||||
|
# Find the extracted directory (tarball might have any root name)
|
||||||
|
EXTRACTED=$(find "$EXTRACT_DIR" -maxdepth 1 -mindepth 1 -type d | head -1)
|
||||||
|
if [[ -z "$EXTRACTED" ]]; then
|
||||||
|
error "Tarball extraction failed — no directory found"
|
||||||
|
rm -rf "$EXTRACT_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$EXTRACTED" "$INSTALL_DIR"
|
||||||
|
rm -rf "$EXTRACT_DIR"
|
||||||
|
|
||||||
|
# Clean up downloaded tarball
|
||||||
|
if [[ -z "$LOCAL_TARBALL" ]] && [[ -f "$TARBALL_PATH" ]]; then
|
||||||
|
rm -f "$TARBALL_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Extracted to ${INSTALL_DIR}"
|
||||||
|
|
||||||
|
# --- Step 5: Run config wizard ---
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}Starting configuration wizard...${NC}"
|
||||||
|
echo ""
|
||||||
|
cd "$INSTALL_DIR"
|
||||||
|
bash config.sh
|
||||||
|
|
||||||
|
# --- Done ---
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}${GREEN}Installation complete!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " Start all services:"
|
||||||
|
echo " cd ${INSTALL_DIR} && docker compose up -d"
|
||||||
|
echo ""
|
||||||
|
echo " Check status:"
|
||||||
|
echo " docker compose ps"
|
||||||
|
echo ""
|
||||||
|
echo " View API logs:"
|
||||||
|
echo " docker compose logs -f api --tail 20"
|
||||||
|
echo ""
|
||||||
@ -24,6 +24,70 @@ done
|
|||||||
cd "$PROJECT_DIR"
|
cd "$PROJECT_DIR"
|
||||||
mkdir -p "$UPGRADE_DIR"
|
mkdir -p "$UPGRADE_DIR"
|
||||||
|
|
||||||
|
# --- Detect install mode ---
|
||||||
|
if [[ -f "$PROJECT_DIR/VERSION" ]] && [[ ! -d "$PROJECT_DIR/.git" ]]; then
|
||||||
|
INSTALL_MODE="release"
|
||||||
|
else
|
||||||
|
INSTALL_MODE="source"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Release mode: check Gitea Releases API ---
|
||||||
|
if [[ "$INSTALL_MODE" == "release" ]]; then
|
||||||
|
GITEA_API="https://gitea.bnkops.com/api/v1"
|
||||||
|
CURRENT_VERSION=$(head -1 "$PROJECT_DIR/VERSION" 2>/dev/null || echo "unknown")
|
||||||
|
CURRENT_SHA=$(sed -n '2p' "$PROJECT_DIR/VERSION" 2>/dev/null || echo "unknown")
|
||||||
|
CURRENT_DATE=$(sed -n '3p' "$PROJECT_DIR/VERSION" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
RELEASE_JSON=$(curl -sf "${GITEA_API}/repos/admin/changemaker.lite/releases/latest" 2>/dev/null || true)
|
||||||
|
if [[ -z "$RELEASE_JSON" ]]; then
|
||||||
|
cat > "$STATUS_FILE" <<EOF
|
||||||
|
{
|
||||||
|
"branch": "release",
|
||||||
|
"currentCommit": "${CURRENT_SHA}",
|
||||||
|
"currentCommitFull": "${CURRENT_SHA}",
|
||||||
|
"currentMessage": "Release ${CURRENT_VERSION}",
|
||||||
|
"currentDate": "${CURRENT_DATE}",
|
||||||
|
"remoteCommit": null,
|
||||||
|
"commitsBehind": 0,
|
||||||
|
"changelog": [],
|
||||||
|
"checkedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||||
|
"error": "Failed to reach Gitea API"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
LATEST_TAG=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tag_name',''))" 2>/dev/null)
|
||||||
|
LATEST_DATE=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('created_at',''))" 2>/dev/null)
|
||||||
|
LATEST_BODY=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body','').replace('\"','\\\\\"')[:200])" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ "$CURRENT_VERSION" == "$LATEST_TAG" ]]; then
|
||||||
|
COMMITS_BEHIND=0
|
||||||
|
else
|
||||||
|
COMMITS_BEHIND=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "$STATUS_FILE" <<EOF
|
||||||
|
{
|
||||||
|
"branch": "release",
|
||||||
|
"currentCommit": "${CURRENT_SHA}",
|
||||||
|
"currentCommitFull": "${CURRENT_SHA}",
|
||||||
|
"currentMessage": "Release ${CURRENT_VERSION}",
|
||||||
|
"currentDate": "${CURRENT_DATE}",
|
||||||
|
"remoteCommit": "${LATEST_TAG}",
|
||||||
|
"remoteCommitFull": "${LATEST_TAG}",
|
||||||
|
"commitsBehind": ${COMMITS_BEHIND},
|
||||||
|
"changelog": [{"hash":"${LATEST_TAG}","message":"${LATEST_BODY}","date":"${LATEST_DATE}","author":"release"}],
|
||||||
|
"checkedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||||
|
"error": null
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
echo "Update check complete (release mode): ${CURRENT_VERSION} → ${LATEST_TAG} (${COMMITS_BEHIND} update(s) available)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Source mode: git-based check ---
|
||||||
|
|
||||||
# Determine branch
|
# Determine branch
|
||||||
if [[ -z "$BRANCH" ]]; then
|
if [[ -z "$BRANCH" ]]; then
|
||||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||||
|
|||||||
@ -39,6 +39,13 @@ USER_PATHS=(
|
|||||||
"nginx/conf.d/services.conf"
|
"nginx/conf.d/services.conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# --- Detect install mode ---
|
||||||
|
if [[ -f "$PROJECT_DIR/VERSION" ]] && [[ ! -d "$PROJECT_DIR/.git" ]]; then
|
||||||
|
INSTALL_MODE="release"
|
||||||
|
else
|
||||||
|
INSTALL_MODE="source"
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Defaults ---
|
# --- Defaults ---
|
||||||
SKIP_BACKUP=false
|
SKIP_BACKUP=false
|
||||||
PULL_SERVICES=false
|
PULL_SERVICES=false
|
||||||
@ -49,6 +56,11 @@ ROLLBACK=false
|
|||||||
API_MODE=false
|
API_MODE=false
|
||||||
USE_REGISTRY=false
|
USE_REGISTRY=false
|
||||||
|
|
||||||
|
# Release installs always use registry mode
|
||||||
|
if [[ "$INSTALL_MODE" == "release" ]]; then
|
||||||
|
USE_REGISTRY=true
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Colors (respects NO_COLOR convention) ---
|
# --- Colors (respects NO_COLOR convention) ---
|
||||||
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
|
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
|
||||||
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
|
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
|
||||||
@ -582,7 +594,67 @@ fi
|
|||||||
phase "3" "Code Update"
|
phase "3" "Code Update"
|
||||||
write_progress 3 "Code Update" 30 "Pulling latest code..."
|
write_progress 3 "Code Update" 30 "Pulling latest code..."
|
||||||
|
|
||||||
if [[ "$DRY_RUN" == "true" ]]; then
|
# --- Release mode: download tarball instead of git pull ---
|
||||||
|
if [[ "$INSTALL_MODE" == "release" ]]; then
|
||||||
|
GITEA_API="${GITEA_REGISTRY_URL:-https://gitea.bnkops.com}/api/v1"
|
||||||
|
CURRENT_VERSION=$(head -1 "$PROJECT_DIR/VERSION" 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
|
info "Release mode — checking for updates (current: ${CURRENT_VERSION})..."
|
||||||
|
RELEASE_JSON=$(curl -sf "${GITEA_API}/repos/admin/changemaker.lite/releases/latest" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ -z "$RELEASE_JSON" ]]; then
|
||||||
|
error "Could not reach Gitea API. Check network or GITEA_REGISTRY_URL."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
LATEST_TAG=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tag_name',''))" 2>/dev/null)
|
||||||
|
TARBALL_URL=$(echo "$RELEASE_JSON" | python3 -c "
|
||||||
|
import sys, json
|
||||||
|
for a in json.load(sys.stdin).get('assets', []):
|
||||||
|
if a['name'].endswith('.tar.gz'):
|
||||||
|
print(a['browser_download_url']); break
|
||||||
|
" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ "$CURRENT_VERSION" == "$LATEST_TAG" ]] && [[ "$FORCE" != "true" ]]; then
|
||||||
|
info "Already at latest version: ${CURRENT_VERSION}"
|
||||||
|
write_progress 3 "Code Update" 45 "Already up to date"
|
||||||
|
elif [[ -z "$TARBALL_URL" ]]; then
|
||||||
|
error "No tarball found in release ${LATEST_TAG}"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
info "Updating ${CURRENT_VERSION} → ${LATEST_TAG}..."
|
||||||
|
write_progress 3 "Code Update" 35 "Downloading ${LATEST_TAG}..."
|
||||||
|
|
||||||
|
# Download
|
||||||
|
DOWNLOAD_DIR=$(mktemp -d)
|
||||||
|
curl -fSL "$TARBALL_URL" -o "${DOWNLOAD_DIR}/update.tar.gz"
|
||||||
|
tar xzf "${DOWNLOAD_DIR}/update.tar.gz" -C "$DOWNLOAD_DIR"
|
||||||
|
UPDATE_SRC=$(find "$DOWNLOAD_DIR" -maxdepth 1 -mindepth 1 -type d | head -1)
|
||||||
|
|
||||||
|
# Save user paths
|
||||||
|
save_user_paths
|
||||||
|
|
||||||
|
# Sync new files, preserving .env
|
||||||
|
write_progress 3 "Code Update" 40 "Applying update..."
|
||||||
|
rsync -a --exclude='.env' "$UPDATE_SRC/" "$PROJECT_DIR/"
|
||||||
|
|
||||||
|
# Restore user paths
|
||||||
|
restore_user_paths
|
||||||
|
|
||||||
|
# Restore tracked files that may have been overwritten
|
||||||
|
DELETED_TRACKED="$(git ls-files --deleted 2>/dev/null || true)"
|
||||||
|
if [[ -n "$DELETED_TRACKED" ]]; then
|
||||||
|
echo "$DELETED_TRACKED" | xargs git checkout HEAD -- 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$DOWNLOAD_DIR"
|
||||||
|
success "Updated to ${LATEST_TAG}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Skip the git-based update flow below
|
||||||
|
POST_PULL_COMMIT="$(head -2 "$PROJECT_DIR/VERSION" | tail -1 2>/dev/null || echo "release")"
|
||||||
|
|
||||||
|
elif [[ "$DRY_RUN" == "true" ]]; then
|
||||||
info "[DRY RUN] Would fetch and show incoming changes:"
|
info "[DRY RUN] Would fetch and show incoming changes:"
|
||||||
git fetch origin "$BRANCH" 2>/dev/null || true
|
git fetch origin "$BRANCH" 2>/dev/null || true
|
||||||
INCOMING="$(git log --oneline HEAD..origin/"$BRANCH" 2>/dev/null || echo "(unable to preview)")"
|
INCOMING="$(git log --oneline HEAD..origin/"$BRANCH" 2>/dev/null || echo "(unable to preview)")"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user