Files
ackify/install/install.sh
Benjamin aa5fee90f6 feat(admin): add option to restrict document creation to admins only
Add new configuration option ACKIFY_ONLY_ADMIN_CAN_CREATE (default: false) to control who can create documents.

Backend changes:
- Add OnlyAdminCanCreate config field to AppConfig
- Implement authorization checks in document handlers
- Protect POST /documents and GET /documents/find-or-create endpoints
- Add unit tests for admin-only document creation (4 tests)

Frontend changes:
- Inject ACKIFY_ONLY_ADMIN_CAN_CREATE to window object
- Hide DocumentForm component for non-admin users when enabled
- Add i18n translations (en, fr, es, de, it)
- Display warning message for non-admin users

Documentation:
- Update .env.example files with new variable
- Update configuration docs (en/fr)
- Update install script to prompt for restriction option
- Update install/README.md

When enabled, only users listed in ACKIFY_ADMIN_EMAILS can create new documents. Both direct creation and find-or-create endpoints are protected.
2025-11-06 16:08:03 +01:00

589 lines
17 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Ackify Community Edition (CE) Installation Script
# Interactive setup for Docker deployment
set -e
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Helper functions
print_header() {
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_info() {
echo -e "${BLUE} $1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
prompt_input() {
local prompt="$1"
local default="$2"
local response
if [ -n "$default" ]; then
read -p "$(echo -e ${BLUE}"$prompt [${default}]: "${NC})" response
echo "${response:-$default}"
else
read -p "$(echo -e ${BLUE}"$prompt: "${NC})" response
echo "$response"
fi
}
prompt_yes_no() {
local prompt="$1"
local default="$2"
local response
if [ "$default" = "y" ]; then
read -p "$(echo -e ${BLUE}"$prompt [Y/n]: "${NC})" response
response="${response:-y}"
else
read -p "$(echo -e ${BLUE}"$prompt [y/N]: "${NC})" response
response="${response:-n}"
fi
[[ "$response" =~ ^[Yy]$ ]]
}
prompt_password() {
local prompt="$1"
local response
read -sp "$(echo -e ${BLUE}"$prompt: "${NC})" response
echo "" >&2
echo "$response"
}
# Main installation
clear
print_header "🔐 Ackify Community Edition (CE) - Interactive Installation"
echo ""
print_info "This script will guide you through the configuration of Ackify CE."
print_info "At least ONE authentication method (OAuth or MagicLink) must be enabled."
echo ""
# Create installation directory
INSTALL_DIR="ackify-ce"
if [ -d "$INSTALL_DIR" ]; then
print_error "Directory $INSTALL_DIR already exists."
if prompt_yes_no "Remove existing directory and continue?" "n"; then
rm -rf "$INSTALL_DIR"
print_success "Existing directory removed"
else
print_error "Installation cancelled"
exit 1
fi
fi
mkdir -p "$INSTALL_DIR"
cd "$INSTALL_DIR"
print_success "Installation directory created: $(pwd)"
echo ""
# ==========================================
# Basic Configuration
# ==========================================
print_header "🌐 Basic Configuration"
echo ""
APP_BASE_URL=$(prompt_input "Application Base URL (e.g., https://ackify.example.com)" "http://localhost:8080")
APP_ORGANISATION=$(prompt_input "Organization Name" "My Organization")
APP_DNS=$(echo "$APP_BASE_URL" | sed -E 's|https?://||')
# Extract domain for email addresses (remove subdomain if present, remove port)
APP_DOMAIN=$(echo "$APP_DNS" | sed 's/:.*//')
# Count dots to detect subdomain
dot_count=$(echo "$APP_DOMAIN" | tr -cd '.' | wc -c)
# If 2+ dots (subdomain.domain.tld), remove first part to keep domain.tld
if [ "$dot_count" -ge 2 ]; then
APP_DOMAIN=$(echo "$APP_DOMAIN" | cut -d. -f2-)
fi
print_success "Base configuration set"
echo ""
# ==========================================
# Traefik Configuration (early choice)
# ==========================================
print_header "🔀 Reverse Proxy Configuration (Traefik)"
echo ""
print_info "Ackify can be configured to work with Traefik for automatic HTTPS."
print_info "If you choose Traefik, ports will not be exposed (Traefik will handle routing)."
echo ""
ENABLE_TRAEFIK=false
COMPOSE_TEMPLATE="compose.yml"
if prompt_yes_no "Use Traefik reverse proxy?" "n"; then
ENABLE_TRAEFIK=true
COMPOSE_TEMPLATE="compose-traefik.yml"
echo ""
TRAEFIK_NETWORK=$(prompt_input "Docker network where Traefik is running" "traefik")
TRAEFIK_CERTRESOLVER=$(prompt_input "Traefik TLS cert resolver name" "letsencrypt")
# Extract app name from DNS
APP_NAME=$(echo "$APP_DNS" | sed 's/\..*//')
print_success "Traefik configuration completed"
echo ""
print_info "Traefik network: ${TRAEFIK_NETWORK}"
print_info "TLS cert resolver: ${TRAEFIK_CERTRESOLVER}"
print_info "App name: ${APP_NAME}"
else
print_info "Standard configuration selected (port 8080 will be exposed)"
fi
echo ""
# Download configuration files
print_header "📦 Downloading Configuration Files"
echo ""
curl -fsSL "https://raw.githubusercontent.com/btouchard/ackify-ce/main/install/${COMPOSE_TEMPLATE}" -o compose.yml
print_success "compose.yml downloaded (${COMPOSE_TEMPLATE})"
curl -fsSL https://raw.githubusercontent.com/btouchard/ackify-ce/main/install/.env.example -o .env.example
print_success ".env.example downloaded"
echo ""
# ==========================================
# OAuth Configuration
# ==========================================
print_header "🔑 OAuth2 Authentication Configuration"
echo ""
print_info "OAuth allows users to sign in with Google, GitHub, GitLab, or custom provider."
echo ""
ENABLE_OAUTH=false
if prompt_yes_no "Enable OAuth2 authentication?" "y"; then
ENABLE_OAUTH=true
echo ""
print_info "Available providers: google, github, gitlab, custom"
OAUTH_PROVIDER=$(prompt_input "OAuth Provider (google/github/gitlab/custom)" "custom")
OAUTH_CLIENT_ID=$(prompt_input "OAuth Client ID")
OAUTH_CLIENT_SECRET=$(prompt_input "OAuth Client Secret")
echo ""
if prompt_yes_no "Restrict to specific email domain? (e.g., @company.com)" "n"; then
OAUTH_ALLOWED_DOMAIN=$(prompt_input "Allowed email domain (e.g., @company.com)" "@example.com")
else
OAUTH_ALLOWED_DOMAIN=""
fi
# Custom provider configuration
if [ "$OAUTH_PROVIDER" = "custom" ]; then
echo ""
print_info "Custom OAuth provider configuration"
OAUTH_AUTH_URL=$(prompt_input "Authorization URL")
OAUTH_TOKEN_URL=$(prompt_input "Token URL")
OAUTH_USERINFO_URL=$(prompt_input "User Info URL")
OAUTH_SCOPES=$(prompt_input "OAuth Scopes" "openid,email,profile")
fi
# GitLab self-hosted
if [ "$OAUTH_PROVIDER" = "gitlab" ]; then
echo ""
if prompt_yes_no "Using self-hosted GitLab?" "n"; then
OAUTH_GITLAB_URL=$(prompt_input "GitLab instance URL" "https://git.company.com")
fi
fi
echo ""
if prompt_yes_no "Enable auto-login (automatically log in if OAuth session exists)?" "n"; then
OAUTH_AUTO_LOGIN="true"
else
OAUTH_AUTO_LOGIN="false"
fi
print_success "OAuth configuration completed"
else
print_warning "OAuth authentication disabled"
fi
echo ""
# ==========================================
# SMTP Configuration
# ==========================================
print_header "📧 SMTP Configuration (Email Service)"
echo ""
print_info "SMTP is used for:"
print_info " - Sending signature reminders to expected signers"
print_info " - MagicLink authentication (passwordless email login)"
echo ""
ENABLE_SMTP=false
if prompt_yes_no "Enable SMTP for email notifications and MagicLink?" "y"; then
ENABLE_SMTP=true
echo ""
MAIL_HOST=$(prompt_input "SMTP Host (e.g., smtp.gmail.com)")
MAIL_PORT=$(prompt_input "SMTP Port" "587")
MAIL_USERNAME=$(prompt_input "SMTP Username")
MAIL_PASSWORD=$(prompt_password "SMTP Password")
echo ""
MAIL_FROM=$(prompt_input "From Email Address" "noreply@${APP_DOMAIN}")
MAIL_FROM_NAME=$(prompt_input "From Name" "$APP_ORGANISATION")
echo ""
# Auto-configure TLS based on port
if [ "$MAIL_PORT" = "465" ]; then
print_info "Port 465 detected - using TLS (implicit SSL)"
MAIL_TLS="true"
MAIL_STARTTLS="false"
elif [ "$MAIL_PORT" = "587" ]; then
print_info "Port 587 detected - using STARTTLS (explicit TLS)"
MAIL_TLS="false"
MAIL_STARTTLS="true"
else
print_warning "Non-standard port detected, please configure TLS manually"
if prompt_yes_no "Use TLS (implicit SSL, typically port 465)?" "n"; then
MAIL_TLS="true"
MAIL_STARTTLS="false"
elif prompt_yes_no "Use STARTTLS (explicit TLS, typically port 587)?" "y"; then
MAIL_TLS="false"
MAIL_STARTTLS="true"
else
MAIL_TLS="false"
MAIL_STARTTLS="false"
fi
fi
print_success "SMTP configuration completed"
echo ""
# MagicLink configuration
print_header "🔗 MagicLink Authentication"
echo ""
print_info "MagicLink provides passwordless authentication via email."
print_info "Since SMTP is configured, MagicLink will be enabled by default."
echo ""
ENABLE_MAGICLINK=true
if prompt_yes_no "Disable MagicLink authentication?" "n"; then
ENABLE_MAGICLINK=false
print_warning "MagicLink authentication will be disabled"
else
print_success "MagicLink authentication enabled"
fi
else
print_warning "SMTP disabled - Email reminders and MagicLink will not be available"
ENABLE_MAGICLINK=false
fi
echo ""
# ==========================================
# Authentication Method Validation
# ==========================================
if [ "$ENABLE_OAUTH" = false ] && [ "$ENABLE_MAGICLINK" = false ]; then
print_error "At least ONE authentication method must be enabled!"
print_error "Please enable OAuth or configure SMTP for MagicLink."
exit 1
fi
# ==========================================
# Admin Configuration
# ==========================================
print_header "👤 Admin Configuration"
echo ""
print_info "Admin users have access to document management and reminder features."
print_info "At least ONE admin email address is required."
echo ""
ADMIN_EMAILS=""
while [ -z "$ADMIN_EMAILS" ]; do
ADMIN_EMAILS=$(prompt_input "Admin email addresses (comma-separated)" "admin@${APP_DOMAIN}")
if [ -z "$ADMIN_EMAILS" ]; then
print_error "At least one admin email is required!"
echo ""
fi
done
print_success "Admin users configured: $ADMIN_EMAILS"
echo ""
# Document Creation Restriction
echo ""
print_info "By default, any authenticated user can create documents."
print_info "You can restrict document creation to admins only."
echo ""
ONLY_ADMIN_CAN_CREATE=false
if prompt_yes_no "Restrict document creation to admins only?" "n"; then
ONLY_ADMIN_CAN_CREATE=true
print_success "Document creation restricted to admins"
else
print_success "All authenticated users can create documents"
fi
echo ""
# ==========================================
# Generate Secrets
# ==========================================
print_header "🔑 Generating Secure Secrets"
echo ""
COOKIE_SECRET=$(openssl rand -base64 32)
print_success "Cookie secret generated"
ED25519_KEY=$(openssl rand 64 | base64 -w 0)
print_success "Ed25519 private key generated"
DB_PASSWORD=$(openssl rand -hex 24)
print_success "Database password generated"
echo ""
# ==========================================
# Create .env file
# ==========================================
print_header "📝 Creating Configuration File"
echo ""
cat > .env <<EOF
# ==========================================
# Ackify Community Edition Configuration
# Generated on $(date)
# ==========================================
# ==========================================
# Application Configuration
# ==========================================
ACKIFY_BASE_URL=${APP_BASE_URL}
ACKIFY_ORGANISATION=${APP_ORGANISATION}
# ==========================================
# Database Configuration
# ==========================================
POSTGRES_USER=ackifyr
POSTGRES_PASSWORD=${DB_PASSWORD}
POSTGRES_DB=ackify
ACKIFY_DB_DSN=postgres://ackifyr:${DB_PASSWORD}@postgres:5432/ackify?sslmode=disable
# ==========================================
# Security Configuration (Auto-generated)
# ==========================================
ACKIFY_OAUTH_COOKIE_SECRET=${COOKIE_SECRET}
ACKIFY_ED25519_PRIVATE_KEY=${ED25519_KEY}
# ==========================================
# Server Configuration
# ==========================================
ACKIFY_LISTEN_ADDR=:8080
ACKIFY_LOG_LEVEL=info
EOF
# OAuth configuration
if [ "$ENABLE_OAUTH" = true ]; then
cat >> .env <<EOF
# ==========================================
# OAuth2 Configuration
# ==========================================
ACKIFY_OAUTH_PROVIDER=${OAUTH_PROVIDER}
ACKIFY_OAUTH_CLIENT_ID=${OAUTH_CLIENT_ID}
ACKIFY_OAUTH_CLIENT_SECRET=${OAUTH_CLIENT_SECRET}
EOF
if [ -n "$OAUTH_ALLOWED_DOMAIN" ]; then
echo "ACKIFY_OAUTH_ALLOWED_DOMAIN=${OAUTH_ALLOWED_DOMAIN}" >> .env
fi
if [ "$OAUTH_PROVIDER" = "custom" ]; then
cat >> .env <<EOF
ACKIFY_OAUTH_AUTH_URL=${OAUTH_AUTH_URL}
ACKIFY_OAUTH_TOKEN_URL=${OAUTH_TOKEN_URL}
ACKIFY_OAUTH_USERINFO_URL=${OAUTH_USERINFO_URL}
ACKIFY_OAUTH_SCOPES=${OAUTH_SCOPES}
EOF
fi
if [ -n "$OAUTH_GITLAB_URL" ]; then
echo "ACKIFY_OAUTH_GITLAB_URL=${OAUTH_GITLAB_URL}" >> .env
fi
echo "ACKIFY_OAUTH_AUTO_LOGIN=${OAUTH_AUTO_LOGIN}" >> .env
echo "ACKIFY_AUTH_OAUTH_ENABLED=true" >> .env
echo "" >> .env
else
# OAuth not configured - explicitly disable
echo "# OAuth2 not configured" >> .env
echo "ACKIFY_AUTH_OAUTH_ENABLED=false" >> .env
echo "" >> .env
fi
# SMTP configuration
if [ "$ENABLE_SMTP" = true ]; then
cat >> .env <<EOF
# ==========================================
# SMTP Configuration (Email Service)
# ==========================================
ACKIFY_MAIL_HOST=${MAIL_HOST}
ACKIFY_MAIL_PORT=${MAIL_PORT}
ACKIFY_MAIL_USERNAME=${MAIL_USERNAME}
ACKIFY_MAIL_PASSWORD=${MAIL_PASSWORD}
ACKIFY_MAIL_FROM=${MAIL_FROM}
ACKIFY_MAIL_FROM_NAME=${MAIL_FROM_NAME}
ACKIFY_MAIL_TLS=${MAIL_TLS}
ACKIFY_MAIL_STARTTLS=${MAIL_STARTTLS}
ACKIFY_MAIL_TIMEOUT=10s
ACKIFY_MAIL_TEMPLATE_DIR=templates
ACKIFY_MAIL_DEFAULT_LOCALE=en
EOF
fi
# MagicLink configuration
if [ "$ENABLE_MAGICLINK" = true ]; then
echo "# MagicLink authentication enabled" >> .env
echo "ACKIFY_AUTH_MAGICLINK_ENABLED=true" >> .env
echo "" >> .env
else
echo "# MagicLink authentication disabled" >> .env
echo "ACKIFY_AUTH_MAGICLINK_ENABLED=false" >> .env
echo "" >> .env
fi
# Admin configuration
if [ -n "$ADMIN_EMAILS" ]; then
cat >> .env <<EOF
# ==========================================
# Admin Configuration
# ==========================================
ACKIFY_ADMIN_EMAILS=${ADMIN_EMAILS}
# Restrict document creation to admins only (default: false)
ACKIFY_ONLY_ADMIN_CAN_CREATE=${ONLY_ADMIN_CAN_CREATE}
EOF
fi
# Traefik configuration
if [ "$ENABLE_TRAEFIK" = true ]; then
cat >> .env <<EOF
# ==========================================
# Traefik Configuration
# ==========================================
TRAEFIK_NETWORK=${TRAEFIK_NETWORK}
TRAEFIK_CERTRESOLVER=${TRAEFIK_CERTRESOLVER}
APP_NAME=${APP_NAME}
APP_DNS=${APP_DNS}
EOF
fi
print_success ".env file created successfully"
echo ""
# ==========================================
# Installation Summary
# ==========================================
print_header "📊 Installation Summary"
echo ""
print_info "Base URL: ${APP_BASE_URL}"
print_info "Organization: ${APP_ORGANISATION}"
echo ""
print_info "Authentication Methods:"
if [ "$ENABLE_OAUTH" = true ]; then
print_success " ✓ OAuth2 ($OAUTH_PROVIDER)"
else
print_warning " ✗ OAuth2 (disabled)"
fi
if [ "$ENABLE_MAGICLINK" = "true" ]; then
print_success " ✓ MagicLink (passwordless email)"
else
print_warning " ✗ MagicLink (disabled)"
fi
echo ""
if [ "$ENABLE_SMTP" = true ]; then
print_success "Email Service: Enabled (${MAIL_HOST})"
else
print_warning "Email Service: Disabled"
fi
echo ""
print_success "Admin Users: ${ADMIN_EMAILS}"
if [ "$ONLY_ADMIN_CAN_CREATE" = true ]; then
print_info "Document Creation: Restricted to admins only"
else
print_info "Document Creation: All authenticated users"
fi
echo ""
if [ "$ENABLE_TRAEFIK" = true ]; then
print_success "Reverse Proxy: Traefik (network: ${TRAEFIK_NETWORK})"
print_info "TLS Certificate: ${TRAEFIK_CERTRESOLVER}"
else
print_info "Reverse Proxy: None (direct port 8080 exposure)"
fi
echo ""
# ==========================================
# Next Steps
# ==========================================
print_header "🚀 Next Steps"
echo ""
print_info "1. Review configuration:"
echo " cat .env"
echo ""
print_info "2. Start Ackify:"
echo " docker compose up -d"
echo ""
print_info "3. Check logs:"
echo " docker compose logs -f ackify-ce"
echo ""
print_info "4. Access application:"
echo " ${APP_BASE_URL}"
echo ""
print_info "5. Check health:"
echo " curl ${APP_BASE_URL}/health"
echo ""
print_header "✅ Installation Complete!"
echo ""
print_success "Installation directory: $(pwd)"
print_success "Configuration file: $(pwd)/.env"
echo ""
if [ "$ENABLE_OAUTH" = false ] && [ "$ENABLE_MAGICLINK" = true ]; then
print_warning "Note: Only MagicLink is enabled. Users will need to receive an email to sign in."
echo ""
fi
print_info "Ready to start Ackify? Run: docker compose up -d"
echo ""