Enhance project wizard with comprehensive options
New features: - Multi-line project brief input (saved to docs/PROJECT_BRIEF.md) - Git remote options: Gitea, GitHub, both, or neither - Syncthing sync/exclude option (auto-updates .stignore) - Network access levels: local, Tailscale, public Internet - Subdomain routing with Traefik config generation - Database selection: SQLite, PostgreSQL, TimescaleDB, Redis - License picker: MIT, Apache 2.0, proprietary - .env.example generation from selected MCPs/databases - Deploy script and deployment docs for web projects - Auto-install gum if missing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,26 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# newproject - Interactive project scaffolding tool
|
||||
# newproject - Interactive project scaffolding wizard
|
||||
# Uses gum for beautiful terminal UI
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
CYAN='\033[0;36m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Config
|
||||
PROJECTS_DIR="$HOME/Projects"
|
||||
TEMPLATE_REPO="git@git.htsn.io:hutson/template-project.git"
|
||||
STIGNORE_FILE="$PROJECTS_DIR/.stignore"
|
||||
|
||||
# Source central config if available
|
||||
[ -f ~/.secrets ] && source ~/.secrets
|
||||
[ -f ~/.hosts ] && source ~/.hosts
|
||||
|
||||
#######################################
|
||||
# Check/Install gum
|
||||
#######################################
|
||||
if ! command -v gum &> /dev/null; then
|
||||
echo "Installing gum (terminal UI toolkit)..."
|
||||
if command -v brew &> /dev/null; then
|
||||
brew install gum
|
||||
else
|
||||
echo "❌ Please install gum: https://github.com/charmbracelet/gum"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Header
|
||||
#######################################
|
||||
@@ -69,11 +75,16 @@ PROJECT_TYPE=$(gum choose \
|
||||
--header "Project Type")
|
||||
|
||||
#######################################
|
||||
# Project Description
|
||||
# Project Brief (Multi-line)
|
||||
#######################################
|
||||
echo ""
|
||||
gum style --foreground 245 "Brief project description:"
|
||||
PROJECT_DESC=$(gum input --placeholder "A brief description of what this project does" --width 60)
|
||||
gum style --foreground 245 "Describe your project (Ctrl+D or Esc when done):"
|
||||
gum style --foreground 240 --italic "Include goals, features, tech preferences, constraints..."
|
||||
echo ""
|
||||
PROJECT_BRIEF=$(gum write --placeholder "Describe your project in detail..." --width 70 --height 8)
|
||||
|
||||
# Extract first line as short description
|
||||
PROJECT_DESC=$(echo "$PROJECT_BRIEF" | head -1)
|
||||
|
||||
#######################################
|
||||
# MCP Selection
|
||||
@@ -81,7 +92,6 @@ PROJECT_DESC=$(gum input --placeholder "A brief description of what this project
|
||||
echo ""
|
||||
gum style --foreground 245 "Select MCPs to enable (space to select, enter to confirm):"
|
||||
|
||||
# Define available MCPs with descriptions
|
||||
MCPS=$(gum choose --no-limit \
|
||||
"exa (web search, research)" \
|
||||
"Ref (documentation lookup)" \
|
||||
@@ -96,18 +106,80 @@ MCPS=$(gum choose --no-limit \
|
||||
--selected "exa (web search, research),Ref (documentation lookup)")
|
||||
|
||||
#######################################
|
||||
# Git Options
|
||||
# Git Remote Options
|
||||
#######################################
|
||||
echo ""
|
||||
gum style --foreground 245 "Git repository options:"
|
||||
|
||||
GIT_INIT=$(gum choose "Yes" "No" --header "Initialize git repository?")
|
||||
|
||||
GITEA_CREATE="No"
|
||||
GIT_REMOTE="None"
|
||||
if [ "$GIT_INIT" = "Yes" ]; then
|
||||
GITEA_CREATE=$(gum choose "Yes" "No" --header "Create repo on Gitea (git.htsn.io)?")
|
||||
GIT_REMOTE=$(gum choose \
|
||||
"Gitea (git.htsn.io) - Private" \
|
||||
"GitHub (github.com) - Public" \
|
||||
"Both (Gitea + GitHub)" \
|
||||
"None (local only)" \
|
||||
--header "Where to push?")
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Syncthing Options
|
||||
#######################################
|
||||
echo ""
|
||||
gum style --foreground 245 "Syncthing sync options:"
|
||||
|
||||
SYNCTHING_OPT=$(gum choose \
|
||||
"Exclude from sync (recommended for git repos)" \
|
||||
"Include in sync" \
|
||||
--header "Sync this project via Syncthing?")
|
||||
|
||||
#######################################
|
||||
# Network/Traefik Options
|
||||
#######################################
|
||||
echo ""
|
||||
gum style --foreground 245 "Does this project need web access (subdomain routing)?"
|
||||
|
||||
NETWORK_ACCESS=$(gum choose \
|
||||
"No web access needed" \
|
||||
"Local network only (10.10.10.x)" \
|
||||
"Local + Tailscale (remote access)" \
|
||||
"Public Internet (via Cloudflare)" \
|
||||
--header "Network access level")
|
||||
|
||||
SUBDOMAIN=""
|
||||
DEPLOY_TARGET=""
|
||||
if [ "$NETWORK_ACCESS" != "No web access needed" ]; then
|
||||
echo ""
|
||||
gum style --foreground 245 "Subdomain for this project:"
|
||||
SUBDOMAIN=$(gum input --placeholder "$PROJECT_NAME" --value "$PROJECT_NAME" --width 30)
|
||||
SUBDOMAIN="${SUBDOMAIN}.htsn.io"
|
||||
|
||||
echo ""
|
||||
gum style --foreground 245 "Deployment target:"
|
||||
DEPLOY_TARGET=$(gum choose \
|
||||
"docker-host (10.10.10.206)" \
|
||||
"trading-vm (10.10.10.221)" \
|
||||
"saltbox (10.10.10.100)" \
|
||||
"Other/Manual" \
|
||||
--header "Where will this deploy?")
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Database Options
|
||||
#######################################
|
||||
echo ""
|
||||
gum style --foreground 245 "Database requirements:"
|
||||
|
||||
DATABASE=$(gum choose \
|
||||
"None" \
|
||||
"SQLite (local file)" \
|
||||
"PostgreSQL" \
|
||||
"TimescaleDB (time-series)" \
|
||||
"Redis (cache/queue)" \
|
||||
"PostgreSQL + Redis" \
|
||||
--header "Database")
|
||||
|
||||
#######################################
|
||||
# Additional Options
|
||||
#######################################
|
||||
@@ -116,28 +188,54 @@ gum style --foreground 245 "Additional options:"
|
||||
|
||||
ADDITIONAL=$(gum choose --no-limit \
|
||||
"spec-kit (spec-driven development)" \
|
||||
"Docker support" \
|
||||
"pre-commit hooks" \
|
||||
"GitHub Actions CI" \
|
||||
"Docker support" \
|
||||
".env.example (from ~/.secrets)" \
|
||||
--header "Additional features (space=select)")
|
||||
|
||||
#######################################
|
||||
# License
|
||||
#######################################
|
||||
echo ""
|
||||
gum style --foreground 245 "License:"
|
||||
|
||||
LICENSE=$(gum choose \
|
||||
"MIT" \
|
||||
"Apache 2.0" \
|
||||
"Proprietary (no license file)" \
|
||||
"None" \
|
||||
--header "License type")
|
||||
|
||||
#######################################
|
||||
# Confirm
|
||||
#######################################
|
||||
echo ""
|
||||
|
||||
# Build summary
|
||||
SUMMARY="📋 Project Summary
|
||||
|
||||
Name: $PROJECT_NAME
|
||||
Type: $PROJECT_TYPE
|
||||
Path: $PROJECT_PATH
|
||||
|
||||
MCPs: $(echo "$MCPS" | tr '\n' ', ' | sed 's/, $//')
|
||||
|
||||
Git: $GIT_INIT | Remote: $GIT_REMOTE
|
||||
Syncthing: $SYNCTHING_OPT
|
||||
Database: $DATABASE
|
||||
License: $LICENSE"
|
||||
|
||||
if [ -n "$SUBDOMAIN" ]; then
|
||||
SUMMARY="$SUMMARY
|
||||
Network: $NETWORK_ACCESS
|
||||
Subdomain: $SUBDOMAIN
|
||||
Deploy to: $DEPLOY_TARGET"
|
||||
fi
|
||||
|
||||
gum style --foreground 212 --border-foreground 212 --border rounded \
|
||||
--align left --width 60 --margin "1 0" --padding "1 2" \
|
||||
"📋 Project Summary" \
|
||||
"" \
|
||||
"Name: $PROJECT_NAME" \
|
||||
"Type: $PROJECT_TYPE" \
|
||||
"Path: $PROJECT_PATH" \
|
||||
"Description: $PROJECT_DESC" \
|
||||
"" \
|
||||
"MCPs: $(echo "$MCPS" | tr '\n' ', ' | sed 's/, $//')" \
|
||||
"" \
|
||||
"Git: $GIT_INIT | Gitea: $GITEA_CREATE" \
|
||||
"Extras: $(echo "$ADDITIONAL" | tr '\n' ', ' | sed 's/, $//')"
|
||||
--align left --width 65 --margin "1 0" --padding "1 2" \
|
||||
"$SUMMARY"
|
||||
|
||||
echo ""
|
||||
CONFIRM=$(gum choose "Create Project" "Cancel" --header "Proceed?")
|
||||
@@ -153,26 +251,35 @@ fi
|
||||
echo ""
|
||||
gum spin --spinner dot --title "Creating project directory..." -- sleep 0.5
|
||||
mkdir -p "$PROJECT_PATH"
|
||||
mkdir -p "$PROJECT_PATH/src"
|
||||
mkdir -p "$PROJECT_PATH/tests"
|
||||
mkdir -p "$PROJECT_PATH/docs"
|
||||
|
||||
#######################################
|
||||
# Generate .claude/settings.json (MCP config)
|
||||
# Save Project Brief
|
||||
#######################################
|
||||
if [ -n "$PROJECT_BRIEF" ]; then
|
||||
gum spin --spinner dot --title "Saving project brief..." -- sleep 0.2
|
||||
cat > "$PROJECT_PATH/docs/PROJECT_BRIEF.md" << BRIEF_EOF
|
||||
# $PROJECT_NAME - Project Brief
|
||||
|
||||
$PROJECT_BRIEF
|
||||
|
||||
---
|
||||
|
||||
*Generated by newproject wizard on $(date '+%Y-%m-%d')*
|
||||
BRIEF_EOF
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Generate .claude/settings.json
|
||||
#######################################
|
||||
gum spin --spinner dot --title "Generating MCP configuration..." -- sleep 0.3
|
||||
|
||||
mkdir -p "$PROJECT_PATH/.claude"
|
||||
|
||||
# Build disabled MCPs list
|
||||
ALL_MCPS=("exa" "Ref" "ticktick" "beeper" "airtable" "claude-in-chrome" "shopping" "proton-mail" "weather")
|
||||
ENABLED_MCPS=""
|
||||
|
||||
# Parse selected MCPs
|
||||
for mcp in "${ALL_MCPS[@]}"; do
|
||||
if echo "$MCPS" | grep -qi "^$mcp "; then
|
||||
ENABLED_MCPS="$ENABLED_MCPS $mcp"
|
||||
fi
|
||||
done
|
||||
|
||||
# Generate settings.json with disabled MCPs
|
||||
cat > "$PROJECT_PATH/.claude/settings.json" << 'SETTINGS_EOF'
|
||||
{
|
||||
"mcpServers": {
|
||||
@@ -201,47 +308,54 @@ SETTINGS_EOF
|
||||
#######################################
|
||||
gum spin --spinner dot --title "Generating CLAUDE.md..." -- sleep 0.3
|
||||
|
||||
# Determine type-specific content
|
||||
case "$PROJECT_TYPE" in
|
||||
python-fastapi)
|
||||
TECH_STACK="Python 3.12+, FastAPI, SQLModel, Pydantic, Alembic"
|
||||
TYPE_CHECK_CMD="mypy --strict src/"
|
||||
LINT_CMD="ruff check src/ && black src/"
|
||||
TEST_CMD="pytest"
|
||||
INSTALL_CMD="pip install -e '.[dev]'"
|
||||
DEV_CMD="uvicorn src.main:app --reload"
|
||||
;;
|
||||
python-generic)
|
||||
TECH_STACK="Python 3.12+"
|
||||
TYPE_CHECK_CMD="mypy --strict src/"
|
||||
LINT_CMD="ruff check src/ && black src/"
|
||||
TEST_CMD="pytest"
|
||||
INSTALL_CMD="pip install -e '.[dev]'"
|
||||
DEV_CMD="python -m src.main"
|
||||
;;
|
||||
typescript-react)
|
||||
TECH_STACK="TypeScript, React 18, Bun, TailwindCSS"
|
||||
TYPE_CHECK_CMD="bun run tsc --noEmit"
|
||||
LINT_CMD="bun run lint"
|
||||
TEST_CMD="bun test"
|
||||
INSTALL_CMD="bun install"
|
||||
DEV_CMD="bun run dev"
|
||||
;;
|
||||
typescript-node)
|
||||
TECH_STACK="TypeScript, Node.js/Bun"
|
||||
TYPE_CHECK_CMD="bun run tsc --noEmit"
|
||||
LINT_CMD="bun run lint"
|
||||
TEST_CMD="bun test"
|
||||
INSTALL_CMD="bun install"
|
||||
DEV_CMD="bun run dev"
|
||||
;;
|
||||
generic)
|
||||
TECH_STACK="[Customize]"
|
||||
TYPE_CHECK_CMD="# Add type check command"
|
||||
LINT_CMD="# Add lint command"
|
||||
TEST_CMD="# Add test command"
|
||||
INSTALL_CMD="# Add install command"
|
||||
DEV_CMD="# Add dev command"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check if spec-kit selected
|
||||
USE_SPECKIT="No"
|
||||
if echo "$ADDITIONAL" | grep -q "spec-kit"; then
|
||||
USE_SPECKIT="Yes"
|
||||
fi
|
||||
|
||||
# Generate CLAUDE.md
|
||||
cat > "$PROJECT_PATH/CLAUDE.md" << CLAUDE_EOF
|
||||
# $PROJECT_NAME - Claude Code Guidelines
|
||||
|
||||
@@ -254,7 +368,7 @@ $PROJECT_DESC
|
||||
**Key Directories**:
|
||||
- \`src/\` - Main source code
|
||||
- \`tests/\` - Test files
|
||||
- \`docs/\` - Documentation
|
||||
- \`docs/\` - Documentation (see \`docs/PROJECT_BRIEF.md\` for full context)
|
||||
|
||||
---
|
||||
|
||||
@@ -308,24 +422,15 @@ Run after completing significant features:
|
||||
## Pre-Commit Checklist
|
||||
|
||||
\`\`\`bash
|
||||
# Type safety
|
||||
$TYPE_CHECK_CMD
|
||||
|
||||
# Linting
|
||||
$LINT_CMD
|
||||
|
||||
# Tests
|
||||
$TEST_CMD
|
||||
|
||||
# Code review (for significant changes)
|
||||
/code-review:code-review
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
CLAUDE_EOF
|
||||
|
||||
# Add spec-kit section if selected
|
||||
if [ "$USE_SPECKIT" = "Yes" ]; then
|
||||
cat >> "$PROJECT_PATH/CLAUDE.md" << 'SPECKIT_EOF'
|
||||
## Spec-Driven Development
|
||||
@@ -345,21 +450,18 @@ if [ "$USE_SPECKIT" = "Yes" ]; then
|
||||
SPECKIT_EOF
|
||||
fi
|
||||
|
||||
# Add MCP configuration section
|
||||
cat >> "$PROJECT_PATH/CLAUDE.md" << MCP_EOF
|
||||
## MCP Configuration
|
||||
|
||||
**Enabled MCPs for this project:**
|
||||
$(echo "$MCPS" | sed 's/^/- /')
|
||||
|
||||
**Disabled MCPs** are configured in \`.claude/settings.json\`. To re-enable, remove from the disabled list or use \`/config\` → MCP Servers.
|
||||
**Disabled MCPs** are configured in \`.claude/settings.json\`.
|
||||
|
||||
---
|
||||
|
||||
## Central Configuration Reference
|
||||
|
||||
This project can access Hutson's central homelab configuration files. These are synced across all machines via Syncthing.
|
||||
|
||||
| File | Purpose | Usage |
|
||||
|------|---------|-------|
|
||||
| \`~/.secrets\` | API keys, tokens, credentials | \`source ~/.secrets\` then use \`\$VAR_NAME\` |
|
||||
@@ -370,13 +472,37 @@ This project can access Hutson's central homelab configuration files. These are
|
||||
1. Add to the central files (\`~/.secrets\` or \`~/.hosts\`)
|
||||
2. Files sync via Syncthing to all machines
|
||||
3. Never commit secrets to git - use environment variables
|
||||
MCP_EOF
|
||||
|
||||
# Add deployment info if applicable
|
||||
if [ -n "$SUBDOMAIN" ]; then
|
||||
cat >> "$PROJECT_PATH/CLAUDE.md" << DEPLOY_EOF
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| **Subdomain** | https://$SUBDOMAIN |
|
||||
| **Deploy Target** | $DEPLOY_TARGET |
|
||||
| **Network Access** | $NETWORK_ACCESS |
|
||||
|
||||
**Deploy command:**
|
||||
\`\`\`bash
|
||||
./scripts/deploy.sh
|
||||
\`\`\`
|
||||
DEPLOY_EOF
|
||||
fi
|
||||
|
||||
cat >> "$PROJECT_PATH/CLAUDE.md" << 'NOTES_EOF'
|
||||
|
||||
---
|
||||
|
||||
## Project-Specific Notes
|
||||
|
||||
[Add project-specific patterns, gotchas, and conventions here]
|
||||
MCP_EOF
|
||||
NOTES_EOF
|
||||
|
||||
#######################################
|
||||
# Generate README.md
|
||||
@@ -392,10 +518,10 @@ $PROJECT_DESC
|
||||
|
||||
\`\`\`bash
|
||||
# Install dependencies
|
||||
# [Add install command]
|
||||
$INSTALL_CMD
|
||||
|
||||
# Run development server
|
||||
# [Add dev command]
|
||||
$DEV_CMD
|
||||
|
||||
# Run tests
|
||||
$TEST_CMD
|
||||
@@ -404,6 +530,7 @@ $TEST_CMD
|
||||
## Development
|
||||
|
||||
See [CLAUDE.md](CLAUDE.md) for development guidelines and conventions.
|
||||
See [docs/PROJECT_BRIEF.md](docs/PROJECT_BRIEF.md) for full project context.
|
||||
README_EOF
|
||||
|
||||
#######################################
|
||||
@@ -451,6 +578,332 @@ htmlcov/
|
||||
logs/
|
||||
GITIGNORE_EOF
|
||||
|
||||
#######################################
|
||||
# Generate .env.example
|
||||
#######################################
|
||||
if echo "$ADDITIONAL" | grep -q ".env.example"; then
|
||||
gum spin --spinner dot --title "Generating .env.example..." -- sleep 0.3
|
||||
|
||||
cat > "$PROJECT_PATH/.env.example" << 'ENVEX_EOF'
|
||||
# Copy this to .env and fill in values
|
||||
# Most values available in ~/.secrets
|
||||
|
||||
# Application
|
||||
ENV=development
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
ENVEX_EOF
|
||||
|
||||
# Add relevant env vars based on selections
|
||||
if echo "$MCPS" | grep -qi "exa"; then
|
||||
echo "# Exa (web search)" >> "$PROJECT_PATH/.env.example"
|
||||
echo "EXA_API_KEY=\${EXA_API_KEY}" >> "$PROJECT_PATH/.env.example"
|
||||
echo "" >> "$PROJECT_PATH/.env.example"
|
||||
fi
|
||||
|
||||
if [ "$DATABASE" = "PostgreSQL" ] || [ "$DATABASE" = "PostgreSQL + Redis" ] || [ "$DATABASE" = "TimescaleDB (time-series)" ]; then
|
||||
echo "# Database" >> "$PROJECT_PATH/.env.example"
|
||||
echo "DATABASE_URL=postgresql://user:password@localhost:5432/$PROJECT_NAME" >> "$PROJECT_PATH/.env.example"
|
||||
echo "" >> "$PROJECT_PATH/.env.example"
|
||||
fi
|
||||
|
||||
if [ "$DATABASE" = "Redis (cache/queue)" ] || [ "$DATABASE" = "PostgreSQL + Redis" ]; then
|
||||
echo "# Redis" >> "$PROJECT_PATH/.env.example"
|
||||
echo "REDIS_URL=redis://localhost:6379" >> "$PROJECT_PATH/.env.example"
|
||||
echo "" >> "$PROJECT_PATH/.env.example"
|
||||
fi
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# License
|
||||
#######################################
|
||||
if [ "$LICENSE" = "MIT" ]; then
|
||||
gum spin --spinner dot --title "Adding MIT license..." -- sleep 0.2
|
||||
cat > "$PROJECT_PATH/LICENSE" << 'MIT_EOF'
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Hutson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
MIT_EOF
|
||||
elif [ "$LICENSE" = "Apache 2.0" ]; then
|
||||
gum spin --spinner dot --title "Adding Apache 2.0 license..." -- sleep 0.2
|
||||
cat > "$PROJECT_PATH/LICENSE" << 'APACHE_EOF'
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright 2026 Hutson
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
APACHE_EOF
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Docker support
|
||||
#######################################
|
||||
if echo "$ADDITIONAL" | grep -q "Docker support"; then
|
||||
gum spin --spinner dot --title "Adding Docker support..." -- sleep 0.3
|
||||
|
||||
if [[ "$PROJECT_TYPE" == python* ]]; then
|
||||
cat > "$PROJECT_PATH/Dockerfile" << 'DOCKER_EOF'
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["python", "-m", "src.main"]
|
||||
DOCKER_EOF
|
||||
else
|
||||
cat > "$PROJECT_PATH/Dockerfile" << 'DOCKER_EOF'
|
||||
FROM oven/bun:1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json bun.lockb ./
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["bun", "run", "start"]
|
||||
DOCKER_EOF
|
||||
fi
|
||||
|
||||
# docker-compose with database if selected
|
||||
cat > "$PROJECT_PATH/docker-compose.yml" << COMPOSE_EOF
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- ENV=development
|
||||
volumes:
|
||||
- .:/app
|
||||
COMPOSE_EOF
|
||||
|
||||
if [ "$DATABASE" = "PostgreSQL" ] || [ "$DATABASE" = "PostgreSQL + Redis" ]; then
|
||||
cat >> "$PROJECT_PATH/docker-compose.yml" << 'COMPOSE_PG_EOF'
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_USER: app
|
||||
POSTGRES_PASSWORD: changeme
|
||||
POSTGRES_DB: app
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
COMPOSE_PG_EOF
|
||||
fi
|
||||
|
||||
if [ "$DATABASE" = "Redis (cache/queue)" ] || [ "$DATABASE" = "PostgreSQL + Redis" ]; then
|
||||
cat >> "$PROJECT_PATH/docker-compose.yml" << 'COMPOSE_REDIS_EOF'
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
COMPOSE_REDIS_EOF
|
||||
fi
|
||||
|
||||
if [ "$DATABASE" = "TimescaleDB (time-series)" ]; then
|
||||
cat >> "$PROJECT_PATH/docker-compose.yml" << 'COMPOSE_TS_EOF'
|
||||
depends_on:
|
||||
- timescaledb
|
||||
|
||||
timescaledb:
|
||||
image: timescale/timescaledb:latest-pg16
|
||||
environment:
|
||||
POSTGRES_USER: app
|
||||
POSTGRES_PASSWORD: changeme
|
||||
POSTGRES_DB: app
|
||||
volumes:
|
||||
- timescale_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
volumes:
|
||||
timescale_data:
|
||||
COMPOSE_TS_EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Spec-kit structure
|
||||
#######################################
|
||||
if [ "$USE_SPECKIT" = "Yes" ]; then
|
||||
gum spin --spinner dot --title "Initializing spec-kit structure..." -- sleep 0.3
|
||||
mkdir -p "$PROJECT_PATH/specs"
|
||||
mkdir -p "$PROJECT_PATH/plans"
|
||||
mkdir -p "$PROJECT_PATH/tasks"
|
||||
touch "$PROJECT_PATH/specs/.gitkeep"
|
||||
touch "$PROJECT_PATH/plans/.gitkeep"
|
||||
touch "$PROJECT_PATH/tasks/.gitkeep"
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Deploy script (if web access needed)
|
||||
#######################################
|
||||
if [ -n "$SUBDOMAIN" ]; then
|
||||
gum spin --spinner dot --title "Creating deploy script..." -- sleep 0.3
|
||||
mkdir -p "$PROJECT_PATH/scripts"
|
||||
|
||||
# Extract deploy host
|
||||
case "$DEPLOY_TARGET" in
|
||||
"docker-host"*) DEPLOY_HOST="docker-host" ;;
|
||||
"trading-vm"*) DEPLOY_HOST="trading-vm" ;;
|
||||
"saltbox"*) DEPLOY_HOST="saltbox" ;;
|
||||
*) DEPLOY_HOST="docker-host" ;;
|
||||
esac
|
||||
|
||||
cat > "$PROJECT_PATH/scripts/deploy.sh" << DEPLOY_EOF
|
||||
#!/usr/bin/env bash
|
||||
# Deploy $PROJECT_NAME to $DEPLOY_HOST
|
||||
|
||||
set -e
|
||||
|
||||
DEPLOY_HOST="$DEPLOY_HOST"
|
||||
PROJECT_NAME="$PROJECT_NAME"
|
||||
SUBDOMAIN="$SUBDOMAIN"
|
||||
|
||||
echo "🚀 Deploying \$PROJECT_NAME to \$DEPLOY_HOST..."
|
||||
|
||||
# Build and push
|
||||
docker build -t \$PROJECT_NAME:latest .
|
||||
|
||||
# Copy to remote (or use registry)
|
||||
# docker save \$PROJECT_NAME:latest | ssh \$DEPLOY_HOST docker load
|
||||
|
||||
echo "✅ Deployed to https://\$SUBDOMAIN"
|
||||
DEPLOY_EOF
|
||||
chmod +x "$PROJECT_PATH/scripts/deploy.sh"
|
||||
|
||||
# Generate Traefik config stub
|
||||
mkdir -p "$PROJECT_PATH/deploy"
|
||||
cat > "$PROJECT_PATH/deploy/traefik.yml" << TRAEFIK_EOF
|
||||
# Traefik dynamic configuration for $PROJECT_NAME
|
||||
# Copy to /opt/traefik/config/$PROJECT_NAME.yml on traefik host
|
||||
|
||||
http:
|
||||
routers:
|
||||
$PROJECT_NAME:
|
||||
rule: "Host(\`$SUBDOMAIN\`)"
|
||||
service: $PROJECT_NAME
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
|
||||
services:
|
||||
$PROJECT_NAME:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://${DEPLOY_HOST}:8000"
|
||||
TRAEFIK_EOF
|
||||
|
||||
# Note about network access in docs
|
||||
cat > "$PROJECT_PATH/docs/DEPLOYMENT.md" << NETDOC_EOF
|
||||
# Deployment Guide
|
||||
|
||||
## Network Access: $NETWORK_ACCESS
|
||||
|
||||
**Subdomain:** https://$SUBDOMAIN
|
||||
**Deploy Target:** $DEPLOY_TARGET
|
||||
|
||||
### Setup Steps
|
||||
|
||||
1. **Deploy the application:**
|
||||
\`\`\`bash
|
||||
./scripts/deploy.sh
|
||||
\`\`\`
|
||||
|
||||
2. **Configure Traefik:**
|
||||
Copy \`deploy/traefik.yml\` to the Traefik config directory:
|
||||
\`\`\`bash
|
||||
scp deploy/traefik.yml traefik:/opt/traefik/config/$PROJECT_NAME.yml
|
||||
\`\`\`
|
||||
|
||||
NETDOC_EOF
|
||||
|
||||
if [ "$NETWORK_ACCESS" = "Public Internet (via Cloudflare)" ]; then
|
||||
cat >> "$PROJECT_PATH/docs/DEPLOYMENT.md" << 'CFDOC_EOF'
|
||||
3. **Add Cloudflare DNS:**
|
||||
- Add A record pointing to your public IP
|
||||
- Or use Cloudflare Tunnel for added security
|
||||
|
||||
4. **Verify SSL:**
|
||||
Traefik will auto-provision Let's Encrypt certificate
|
||||
CFDOC_EOF
|
||||
elif [ "$NETWORK_ACCESS" = "Local + Tailscale (remote access)" ]; then
|
||||
cat >> "$PROJECT_PATH/docs/DEPLOYMENT.md" << 'TSDOC_EOF'
|
||||
3. **Tailscale Access:**
|
||||
- Ensure deploy target is on Tailscale network
|
||||
- Access via Tailscale IP or MagicDNS name
|
||||
|
||||
4. **No public DNS needed** - access via Tailscale only
|
||||
TSDOC_EOF
|
||||
else
|
||||
cat >> "$PROJECT_PATH/docs/DEPLOYMENT.md" << 'LOCDOC_EOF'
|
||||
3. **Local Access Only:**
|
||||
- Add entry to local DNS (Pi-hole) or /etc/hosts
|
||||
- Access only from 10.10.10.x network
|
||||
LOCDOC_EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Syncthing Configuration
|
||||
#######################################
|
||||
if [ "$SYNCTHING_OPT" = "Exclude from sync (recommended for git repos)" ]; then
|
||||
gum spin --spinner dot --title "Adding to Syncthing ignore list..." -- sleep 0.3
|
||||
|
||||
if [ -f "$STIGNORE_FILE" ]; then
|
||||
# Check if already in ignore file
|
||||
if ! grep -q "^$PROJECT_NAME$" "$STIGNORE_FILE" 2>/dev/null; then
|
||||
echo "$PROJECT_NAME" >> "$STIGNORE_FILE"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Git Init
|
||||
#######################################
|
||||
@@ -467,81 +920,47 @@ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>'"
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Gitea Repo
|
||||
# Git Remotes
|
||||
#######################################
|
||||
if [ "$GITEA_CREATE" = "Yes" ] && [ -n "$GITEA_TOKEN" ]; then
|
||||
GITEA_URL=""
|
||||
GITHUB_URL=""
|
||||
|
||||
if [[ "$GIT_REMOTE" == *"Gitea"* ]] && [ -n "$GITEA_TOKEN" ]; then
|
||||
gum spin --spinner dot --title "Creating Gitea repository..." -- sleep 0.5
|
||||
|
||||
# Create repo via API
|
||||
RESPONSE=$(curl -s -X POST "https://git.htsn.io/api/v1/user/repos" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"name\": \"$PROJECT_NAME\",
|
||||
\"description\": \"$PROJECT_DESC\",
|
||||
\"private\": false,
|
||||
\"private\": true,
|
||||
\"auto_init\": false
|
||||
}" 2>/dev/null)
|
||||
|
||||
if echo "$RESPONSE" | grep -q "\"name\":\"$PROJECT_NAME\""; then
|
||||
# Add remote and push
|
||||
git -C "$PROJECT_PATH" remote add origin "git@git.htsn.io:hutson/$PROJECT_NAME.git" 2>/dev/null || true
|
||||
gum spin --spinner dot --title "Pushing to Gitea..." -- \
|
||||
git -C "$PROJECT_PATH" push -u origin main -q 2>/dev/null || true
|
||||
GITEA_URL="https://git.htsn.io/hutson/$PROJECT_NAME"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$GIT_REMOTE" == *"GitHub"* ]]; then
|
||||
if command -v gh &> /dev/null; then
|
||||
gum spin --spinner dot --title "Creating GitHub repository..." -- sleep 0.5
|
||||
|
||||
gh repo create "$PROJECT_NAME" --public --source="$PROJECT_PATH" --push 2>/dev/null || true
|
||||
GITHUB_URL="https://github.com/$(gh api user -q .login)/$PROJECT_NAME"
|
||||
|
||||
# If both remotes, rename
|
||||
if [ -n "$GITEA_URL" ]; then
|
||||
git -C "$PROJECT_PATH" remote rename origin gitea 2>/dev/null || true
|
||||
git -C "$PROJECT_PATH" remote add github "git@github.com:$(gh api user -q .login)/$PROJECT_NAME.git" 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
GITEA_URL="(failed to create)"
|
||||
GITHUB_URL="(gh CLI not installed - create manually)"
|
||||
fi
|
||||
elif [ "$GITEA_CREATE" = "Yes" ]; then
|
||||
GITEA_URL="(GITEA_TOKEN not set in ~/.secrets)"
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Spec-kit init
|
||||
#######################################
|
||||
if [ "$USE_SPECKIT" = "Yes" ]; then
|
||||
gum spin --spinner dot --title "Initializing spec-kit structure..." -- sleep 0.3
|
||||
mkdir -p "$PROJECT_PATH/specs"
|
||||
mkdir -p "$PROJECT_PATH/plans"
|
||||
mkdir -p "$PROJECT_PATH/tasks"
|
||||
touch "$PROJECT_PATH/specs/.gitkeep"
|
||||
touch "$PROJECT_PATH/plans/.gitkeep"
|
||||
touch "$PROJECT_PATH/tasks/.gitkeep"
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Docker support
|
||||
#######################################
|
||||
if echo "$ADDITIONAL" | grep -q "Docker support"; then
|
||||
gum spin --spinner dot --title "Adding Docker support..." -- sleep 0.3
|
||||
|
||||
cat > "$PROJECT_PATH/Dockerfile" << 'DOCKER_EOF'
|
||||
# Customize this Dockerfile for your project
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["python", "-m", "src.main"]
|
||||
DOCKER_EOF
|
||||
|
||||
cat > "$PROJECT_PATH/docker-compose.yml" << 'COMPOSE_EOF'
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- ENV=development
|
||||
volumes:
|
||||
- .:/app
|
||||
COMPOSE_EOF
|
||||
fi
|
||||
|
||||
#######################################
|
||||
@@ -558,15 +977,27 @@ echo ""
|
||||
gum style --foreground 245 "Next steps:"
|
||||
echo ""
|
||||
echo " cd $PROJECT_PATH"
|
||||
|
||||
if [ "$USE_SPECKIT" = "Yes" ]; then
|
||||
echo " /speckit.specify # Define requirements"
|
||||
else
|
||||
echo " # Review docs/PROJECT_BRIEF.md"
|
||||
echo " # Start coding!"
|
||||
fi
|
||||
|
||||
if [ -n "$GITEA_URL" ] && [ "$GITEA_URL" != "(failed to create)" ] && [ "$GITEA_URL" != "(GITEA_TOKEN not set in ~/.secrets)" ]; then
|
||||
if [ -n "$GITEA_URL" ] && [ "$GITEA_URL" != "(failed)" ]; then
|
||||
echo ""
|
||||
echo " Gitea: $GITEA_URL"
|
||||
fi
|
||||
|
||||
if [ -n "$GITHUB_URL" ] && [[ "$GITHUB_URL" != *"not installed"* ]]; then
|
||||
echo ""
|
||||
echo " GitHub: $GITHUB_URL"
|
||||
fi
|
||||
|
||||
if [ -n "$SUBDOMAIN" ]; then
|
||||
echo ""
|
||||
echo " Subdomain: https://$SUBDOMAIN (configure Traefik)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user