Upload files to "/"
This commit is contained in:
@@ -0,0 +1,529 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# ==============================================================================
|
||||
# AppFactory Bootstrap for Ubuntu Server
|
||||
# ==============================================================================
|
||||
# What this script does:
|
||||
# - Installs base packages
|
||||
# - Installs Docker Engine + Docker Compose plugin
|
||||
# - Creates /opt/appfactory structure
|
||||
# - Creates Docker Compose stack:
|
||||
# - Caddy reverse proxy
|
||||
# - Gitea Git server
|
||||
# - Docker Registry
|
||||
# - Creates initial Gitea app.ini so web setup is skipped
|
||||
# - Starts the stack
|
||||
# - Optionally creates Gitea admin user
|
||||
#
|
||||
# Run:
|
||||
# chmod +x bootstrap-appfactory-ubuntu.sh
|
||||
# sudo ./bootstrap-appfactory-ubuntu.sh
|
||||
#
|
||||
# Recommended:
|
||||
# edit variables below before running on a real server
|
||||
# ==============================================================================
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Editable variables
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
APP_USER="${APP_USER:-${SUDO_USER:-jiri}}"
|
||||
BASE_DIR="${BASE_DIR:-/opt/appfactory}"
|
||||
|
||||
# Server IP/domain used in URLs.
|
||||
# If empty, script tries to detect first IPv4 address.
|
||||
SERVER_HOST="${SERVER_HOST:-}"
|
||||
|
||||
GITEA_SITE_NAME="${GITEA_SITE_NAME:-AppFactory Git}"
|
||||
GITEA_ADMIN_USER="${GITEA_ADMIN_USER:-jiri}"
|
||||
GITEA_ADMIN_EMAIL="${GITEA_ADMIN_EMAIL:-jiri.uhlir59@gmail.com}"
|
||||
|
||||
# CHANGE THIS before production use.
|
||||
GITEA_ADMIN_PASSWORD="${GITEA_ADMIN_PASSWORD:-ChangeMe123!}"
|
||||
|
||||
# Ports
|
||||
HTTP_PORT="${HTTP_PORT:-80}"
|
||||
HTTPS_PORT="${HTTPS_PORT:-443}"
|
||||
GITEA_HTTP_PORT="${GITEA_HTTP_PORT:-3000}"
|
||||
GITEA_SSH_PORT="${GITEA_SSH_PORT:-2222}"
|
||||
REGISTRY_PORT="${REGISTRY_PORT:-5000}"
|
||||
|
||||
# Docker images
|
||||
CADDY_IMAGE="${CADDY_IMAGE:-caddy:2}"
|
||||
GITEA_IMAGE="${GITEA_IMAGE:-gitea/gitea:latest}"
|
||||
REGISTRY_IMAGE="${REGISTRY_IMAGE:-registry:2}"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
log() {
|
||||
echo ""
|
||||
echo "== $* =="
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [ "${EUID}" -ne 0 ]; then
|
||||
fail "Run as root: sudo ./bootstrap-appfactory-ubuntu.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
detect_server_host() {
|
||||
if [ -z "${SERVER_HOST}" ]; then
|
||||
SERVER_HOST="$(hostname -I | awk '{print $1}')"
|
||||
fi
|
||||
|
||||
if [ -z "${SERVER_HOST}" ]; then
|
||||
fail "Could not detect SERVER_HOST. Run with SERVER_HOST=your-ip-or-domain"
|
||||
fi
|
||||
}
|
||||
|
||||
ubuntu_codename_for_docker() {
|
||||
local codename
|
||||
codename="$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")"
|
||||
|
||||
# Docker may not immediately support future Ubuntu releases.
|
||||
# Fallback to noble for unsupported future/testing versions.
|
||||
case "$codename" in
|
||||
jammy|noble)
|
||||
echo "$codename"
|
||||
;;
|
||||
*)
|
||||
echo "noble"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
wait_for_gitea() {
|
||||
log "Waiting for Gitea"
|
||||
for i in $(seq 1 60); do
|
||||
if curl -fsS "http://127.0.0.1:${GITEA_HTTP_PORT}/api/healthz" >/dev/null 2>&1; then
|
||||
echo "Gitea is responding."
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Gitea did not respond on /api/healthz yet. Continuing anyway."
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Main
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
require_root
|
||||
detect_server_host
|
||||
|
||||
log "AppFactory bootstrap"
|
||||
echo "APP_USER: ${APP_USER}"
|
||||
echo "BASE_DIR: ${BASE_DIR}"
|
||||
echo "SERVER_HOST: ${SERVER_HOST}"
|
||||
echo "Gitea URL: http://${SERVER_HOST}:${GITEA_HTTP_PORT}/"
|
||||
echo "Registry URL: http://${SERVER_HOST}:${REGISTRY_PORT}/v2/"
|
||||
echo "Gateway health URL: http://${SERVER_HOST}/health"
|
||||
|
||||
log "Install base packages"
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
git \
|
||||
nano \
|
||||
unzip \
|
||||
tree \
|
||||
ufw \
|
||||
jq
|
||||
|
||||
log "Install Docker Engine"
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
|
||||
if [ ! -f /etc/apt/keyrings/docker.asc ]; then
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||
chmod a+r /etc/apt/keyrings/docker.asc
|
||||
fi
|
||||
|
||||
DOCKER_CODENAME="$(ubuntu_codename_for_docker)"
|
||||
|
||||
cat > /etc/apt/sources.list.d/docker.list <<EOF_DOCKER_REPO
|
||||
deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${DOCKER_CODENAME} stable
|
||||
EOF_DOCKER_REPO
|
||||
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
docker-ce \
|
||||
docker-ce-cli \
|
||||
containerd.io \
|
||||
docker-buildx-plugin \
|
||||
docker-compose-plugin
|
||||
|
||||
systemctl enable docker
|
||||
systemctl start docker
|
||||
|
||||
if id "${APP_USER}" >/dev/null 2>&1; then
|
||||
usermod -aG docker "${APP_USER}" || true
|
||||
fi
|
||||
|
||||
log "Create AppFactory folders"
|
||||
mkdir -p \
|
||||
"${BASE_DIR}/apps" \
|
||||
"${BASE_DIR}/services" \
|
||||
"${BASE_DIR}/templates" \
|
||||
"${BASE_DIR}/deploy" \
|
||||
"${BASE_DIR}/deploy/env" \
|
||||
"${BASE_DIR}/gateway" \
|
||||
"${BASE_DIR}/docs" \
|
||||
"${BASE_DIR}/tools" \
|
||||
"${BASE_DIR}/data/gitea/gitea/conf" \
|
||||
"${BASE_DIR}/data/gitea/git" \
|
||||
"${BASE_DIR}/data/registry" \
|
||||
"${BASE_DIR}/data/caddy" \
|
||||
"${BASE_DIR}/data/caddy-config"
|
||||
|
||||
log "Create Gitea config"
|
||||
cat > "${BASE_DIR}/data/gitea/gitea/conf/app.ini" <<EOF_GITEA
|
||||
APP_NAME = ${GITEA_SITE_NAME}
|
||||
RUN_USER = git
|
||||
WORK_PATH = /data/gitea
|
||||
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
PATH = /data/gitea/gitea.db
|
||||
|
||||
[repository]
|
||||
ROOT = /data/git/repositories
|
||||
|
||||
[server]
|
||||
DOMAIN = ${SERVER_HOST}
|
||||
HTTP_PORT = 3000
|
||||
ROOT_URL = http://${SERVER_HOST}:${GITEA_HTTP_PORT}/
|
||||
DISABLE_SSH = false
|
||||
SSH_DOMAIN = ${SERVER_HOST}
|
||||
SSH_PORT = ${GITEA_SSH_PORT}
|
||||
START_SSH_SERVER = false
|
||||
LFS_START_SERVER = true
|
||||
LFS_JWT_SECRET = appfactory-lfs-secret-change-me
|
||||
OFFLINE_MODE = true
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = appfactory-secret-key-change-me
|
||||
INTERNAL_TOKEN = appfactory-internal-token-change-me
|
||||
PASSWORD_HASH_ALGO = pbkdf2
|
||||
|
||||
[service]
|
||||
DISABLE_REGISTRATION = false
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
REGISTER_EMAIL_CONFIRM = false
|
||||
ENABLE_NOTIFY_MAIL = false
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
||||
ENABLE_CAPTCHA = false
|
||||
|
||||
[openid]
|
||||
ENABLE_OPENID_SIGNIN = false
|
||||
ENABLE_OPENID_SIGNUP = false
|
||||
|
||||
[mailer]
|
||||
ENABLED = false
|
||||
|
||||
[log]
|
||||
MODE = console
|
||||
LEVEL = info
|
||||
ROOT_PATH = /data/gitea/log
|
||||
EOF_GITEA
|
||||
|
||||
log "Create Caddyfile"
|
||||
cat > "${BASE_DIR}/gateway/Caddyfile" <<'EOF_CADDY'
|
||||
:80 {
|
||||
respond /health "AppFactory OK" 200
|
||||
|
||||
handle_path /registry/* {
|
||||
reverse_proxy registry:5000
|
||||
}
|
||||
|
||||
handle {
|
||||
respond "AppFactory gateway is running" 200
|
||||
}
|
||||
}
|
||||
EOF_CADDY
|
||||
|
||||
log "Create docker-compose.yml"
|
||||
cat > "${BASE_DIR}/deploy/docker-compose.yml" <<EOF_COMPOSE
|
||||
services:
|
||||
caddy:
|
||||
image: ${CADDY_IMAGE}
|
||||
container_name: appfactory-caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${HTTP_PORT}:80"
|
||||
- "${HTTPS_PORT}:443"
|
||||
volumes:
|
||||
- ../gateway/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ../data/caddy:/data
|
||||
- ../data/caddy-config:/config
|
||||
networks:
|
||||
- appfactory
|
||||
|
||||
gitea:
|
||||
image: ${GITEA_IMAGE}
|
||||
container_name: appfactory-gitea
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- GITEA__server__ROOT_URL=http://${SERVER_HOST}:${GITEA_HTTP_PORT}/
|
||||
- GITEA__server__SSH_PORT=${GITEA_SSH_PORT}
|
||||
- GITEA__security__INSTALL_LOCK=true
|
||||
volumes:
|
||||
- ../data/gitea:/data
|
||||
ports:
|
||||
- "${GITEA_HTTP_PORT}:3000"
|
||||
- "${GITEA_SSH_PORT}:22"
|
||||
networks:
|
||||
- appfactory
|
||||
|
||||
registry:
|
||||
image: ${REGISTRY_IMAGE}
|
||||
container_name: appfactory-registry
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ../data/registry:/var/lib/registry
|
||||
ports:
|
||||
- "${REGISTRY_PORT}:5000"
|
||||
networks:
|
||||
- appfactory
|
||||
|
||||
networks:
|
||||
appfactory:
|
||||
name: appfactory
|
||||
EOF_COMPOSE
|
||||
|
||||
log "Create App Catalog"
|
||||
cat > "${BASE_DIR}/apps/catalog.yml" <<'EOF_CATALOG'
|
||||
apps: []
|
||||
EOF_CATALOG
|
||||
|
||||
log "Create documentation"
|
||||
cat > "${BASE_DIR}/docs/README.md" <<EOF_DOCS
|
||||
# AppFactory
|
||||
|
||||
Interní platforma pro Apps/mikroservisy.
|
||||
|
||||
## URLs
|
||||
|
||||
- Gitea: http://${SERVER_HOST}:${GITEA_HTTP_PORT}
|
||||
- Registry: http://${SERVER_HOST}:${REGISTRY_PORT}/v2/
|
||||
- Gateway health: http://${SERVER_HOST}/health
|
||||
|
||||
## Runtime folders
|
||||
|
||||
- Platform: ${BASE_DIR}
|
||||
- Compose: ${BASE_DIR}/deploy/docker-compose.yml
|
||||
- Caddyfile: ${BASE_DIR}/gateway/Caddyfile
|
||||
- Gitea data: ${BASE_DIR}/data/gitea
|
||||
- Registry data: ${BASE_DIR}/data/registry
|
||||
|
||||
## Commands
|
||||
|
||||
Start:
|
||||
|
||||
\`\`\`bash
|
||||
cd ${BASE_DIR}/deploy
|
||||
docker compose up -d
|
||||
\`\`\`
|
||||
|
||||
Stop:
|
||||
|
||||
\`\`\`bash
|
||||
cd ${BASE_DIR}/deploy
|
||||
docker compose down
|
||||
\`\`\`
|
||||
|
||||
Status:
|
||||
|
||||
\`\`\`bash
|
||||
docker ps
|
||||
\`\`\`
|
||||
EOF_DOCS
|
||||
|
||||
log "Create Python FastAPI template folder"
|
||||
mkdir -p "${BASE_DIR}/templates/python-fastapi-template/app"
|
||||
|
||||
cat > "${BASE_DIR}/templates/python-fastapi-template/app/main.py" <<'EOF_FASTAPI'
|
||||
from fastapi import FastAPI
|
||||
|
||||
APP_NAME = "Python FastAPI Template"
|
||||
APP_VERSION = "1.0.0"
|
||||
|
||||
app = FastAPI(
|
||||
title=APP_NAME,
|
||||
version=APP_VERSION
|
||||
)
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.get("/version")
|
||||
def version():
|
||||
return {
|
||||
"app": APP_NAME,
|
||||
"version": APP_VERSION,
|
||||
"language": "python"
|
||||
}
|
||||
EOF_FASTAPI
|
||||
|
||||
cat > "${BASE_DIR}/templates/python-fastapi-template/requirements.txt" <<'EOF_REQ'
|
||||
fastapi
|
||||
uvicorn[standard]
|
||||
EOF_REQ
|
||||
|
||||
cat > "${BASE_DIR}/templates/python-fastapi-template/Dockerfile" <<'EOF_DOCKERFILE'
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY app ./app
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
EOF_DOCKERFILE
|
||||
|
||||
cat > "${BASE_DIR}/templates/python-fastapi-template/README.md" <<'EOF_TEMPLATE_README'
|
||||
# Python FastAPI Template
|
||||
|
||||
Default template for AppFactory Python Apps.
|
||||
|
||||
## Local run
|
||||
|
||||
```bash
|
||||
docker build -t python-fastapi-template .
|
||||
docker run --rm -p 8000:8000 python-fastapi-template
|
||||
```
|
||||
|
||||
Open:
|
||||
|
||||
```text
|
||||
http://SERVER_IP:8000/docs
|
||||
```
|
||||
EOF_TEMPLATE_README
|
||||
|
||||
log "Create new-app tool"
|
||||
cat > "${BASE_DIR}/tools/new-app.sh" <<'EOF_NEW_APP'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
APP_ID="${1:-}"
|
||||
|
||||
if [ -z "$APP_ID" ]; then
|
||||
echo "Usage:"
|
||||
echo " ./new-app.sh <app-id>"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " ./new-app.sh gmail-app"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASE_DIR="${BASE_DIR:-/opt/appfactory}"
|
||||
SOURCE_TEMPLATE="${BASE_DIR}/templates/python-fastapi-template"
|
||||
TARGET_DIR="${BASE_DIR}/services/${APP_ID}"
|
||||
|
||||
if [ -d "$TARGET_DIR" ]; then
|
||||
echo "App already exists: $TARGET_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp -R "$SOURCE_TEMPLATE" "$TARGET_DIR"
|
||||
|
||||
sed -i "s/Python FastAPI Template/${APP_ID}/g" "$TARGET_DIR/app/main.py"
|
||||
|
||||
cat > "$TARGET_DIR/app.yml" <<EOF_APP
|
||||
id: ${APP_ID}
|
||||
name: ${APP_ID}
|
||||
language: python
|
||||
version: 1.0.0
|
||||
base_path: /${APP_ID}
|
||||
port: 8000
|
||||
status: development
|
||||
EOF_APP
|
||||
|
||||
echo ""
|
||||
echo "Created app:"
|
||||
echo " $TARGET_DIR"
|
||||
echo ""
|
||||
echo "Next:"
|
||||
echo " cd $TARGET_DIR"
|
||||
echo " docker build -t $APP_ID ."
|
||||
echo " docker run --rm -p 8001:8000 $APP_ID"
|
||||
EOF_NEW_APP
|
||||
|
||||
chmod +x "${BASE_DIR}/tools/new-app.sh"
|
||||
|
||||
log "Set permissions"
|
||||
if id "${APP_USER}" >/dev/null 2>&1; then
|
||||
chown -R "${APP_USER}:${APP_USER}" "${BASE_DIR}"
|
||||
fi
|
||||
|
||||
log "Firewall"
|
||||
ufw allow OpenSSH || true
|
||||
ufw allow "${HTTP_PORT}/tcp" || true
|
||||
ufw allow "${HTTPS_PORT}/tcp" || true
|
||||
ufw allow "${GITEA_HTTP_PORT}/tcp" || true
|
||||
ufw allow "${GITEA_SSH_PORT}/tcp" || true
|
||||
ufw allow "${REGISTRY_PORT}/tcp" || true
|
||||
ufw --force enable || true
|
||||
|
||||
log "Start AppFactory stack"
|
||||
cd "${BASE_DIR}/deploy"
|
||||
docker compose up -d
|
||||
|
||||
wait_for_gitea
|
||||
|
||||
log "Create Gitea admin user if missing"
|
||||
if docker exec appfactory-gitea gitea admin user list --config /data/gitea/conf/app.ini 2>/dev/null | grep -q "${GITEA_ADMIN_USER}"; then
|
||||
echo "Gitea admin user already exists: ${GITEA_ADMIN_USER}"
|
||||
else
|
||||
docker exec appfactory-gitea gitea admin user create \
|
||||
--config /data/gitea/conf/app.ini \
|
||||
--admin \
|
||||
--username "${GITEA_ADMIN_USER}" \
|
||||
--password "${GITEA_ADMIN_PASSWORD}" \
|
||||
--email "${GITEA_ADMIN_EMAIL}" \
|
||||
--must-change-password=false || true
|
||||
fi
|
||||
|
||||
log "Final status"
|
||||
docker ps
|
||||
|
||||
cat <<EOF_DONE
|
||||
|
||||
DONE
|
||||
|
||||
Open:
|
||||
Gitea: http://${SERVER_HOST}:${GITEA_HTTP_PORT}
|
||||
Registry: http://${SERVER_HOST}:${REGISTRY_PORT}/v2/
|
||||
Health: http://${SERVER_HOST}/health
|
||||
|
||||
Gitea admin:
|
||||
username: ${GITEA_ADMIN_USER}
|
||||
password: ${GITEA_ADMIN_PASSWORD}
|
||||
|
||||
Useful commands:
|
||||
cd ${BASE_DIR}/deploy && docker compose ps
|
||||
cd ${BASE_DIR}/deploy && docker compose logs -f
|
||||
${BASE_DIR}/tools/new-app.sh gmail-app
|
||||
|
||||
IMPORTANT:
|
||||
If this was first Docker install, log out and log in again so user '${APP_USER}' gets docker permissions.
|
||||
|
||||
EOF_DONE
|
||||
Reference in New Issue
Block a user