Add newproject command and fix template bugs
- Add newproject, ssh, templates commands for full project scaffolding - Add homelab SSH, Caddy, port, and proxy_host fields to Config - Fix dotEnvExample: use container-internal port (3000/8000/8080) not host port - Fix caddySnippet: use configurable proxy_host instead of localhost - Fix dockerComposeTemplate: add build: path so image-less deploys work - Commit gtea.exe binary
This commit is contained in:
226
cmd/templates.go
Normal file
226
cmd/templates.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package cmd
|
||||
|
||||
import "fmt"
|
||||
|
||||
func gitignoreFor(stackType string) string {
|
||||
base := ".env\n.env.*\n!.env.example\n"
|
||||
switch stackType {
|
||||
case "node":
|
||||
return base + "node_modules/\ndist/\n.next/\n*.log\n"
|
||||
case "python":
|
||||
return base + "__pycache__/\n*.pyc\n.venv/\nvenv/\n*.egg-info/\ndist/\nbuild/\n"
|
||||
case "go":
|
||||
return base + "*.exe\n*.test\n*.out\n/vendor/\n"
|
||||
case "static":
|
||||
return base + "dist/\n.cache/\n"
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func dockerComposeTemplate(name, stackType string, port int) string {
|
||||
projectsDir := "~/projects"
|
||||
var svc string
|
||||
switch stackType {
|
||||
case "node":
|
||||
svc = fmt.Sprintf(
|
||||
" app:\n image: ${IMAGE:-ghcr.io/sketchferret/%s:latest}\n"+
|
||||
" build: %s/%s\n"+
|
||||
" restart: unless-stopped\n ports:\n - \"%d:3000\"\n env_file: .env\n",
|
||||
name, projectsDir, name, port)
|
||||
case "python":
|
||||
svc = fmt.Sprintf(
|
||||
" app:\n image: ${IMAGE:-ghcr.io/sketchferret/%s:latest}\n"+
|
||||
" build: %s/%s\n"+
|
||||
" restart: unless-stopped\n ports:\n - \"%d:8000\"\n env_file: .env\n",
|
||||
name, projectsDir, name, port)
|
||||
case "go":
|
||||
svc = fmt.Sprintf(
|
||||
" app:\n image: ${IMAGE:-ghcr.io/sketchferret/%s:latest}\n"+
|
||||
" build: %s/%s\n"+
|
||||
" restart: unless-stopped\n ports:\n - \"%d:8080\"\n env_file: .env\n",
|
||||
name, projectsDir, name, port)
|
||||
case "static":
|
||||
svc = fmt.Sprintf(
|
||||
" web:\n image: nginx:alpine\n restart: unless-stopped\n"+
|
||||
" ports:\n - \"%d:80\"\n volumes:\n - ./dist:/usr/share/nginx/html:ro\n",
|
||||
port)
|
||||
}
|
||||
return "services:\n" + svc
|
||||
}
|
||||
|
||||
func dotEnvExample(name, stackType string, port int) string {
|
||||
_ = port // host port is set by compose; container uses its own internal port
|
||||
header := fmt.Sprintf("# %s environment\n# Copy to .env and fill in values\n\n", name)
|
||||
switch stackType {
|
||||
case "node":
|
||||
return header + "PORT=3000\n# DATABASE_URL=\n# SECRET_KEY=\n"
|
||||
case "python":
|
||||
return header + "PORT=8000\n# DATABASE_URL=\n# SECRET_KEY=\n"
|
||||
case "go":
|
||||
return header + "PORT=8080\n# DATABASE_URL=\n"
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
func caddySnippet(subdomain, proxyHost string, port int) string {
|
||||
return fmt.Sprintf("%s {\n\treverse_proxy %s:%d\n}\n", subdomain, proxyHost, port)
|
||||
}
|
||||
|
||||
// --- local starter files ---
|
||||
|
||||
func nodePackageJSON(name string) string {
|
||||
return fmt.Sprintf(`{
|
||||
"name": "%s",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"dev": "ts-node src/index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.0.0",
|
||||
"ts-node": "^10.0.0",
|
||||
"@types/node": "^20.0.0"
|
||||
}
|
||||
}
|
||||
`, name)
|
||||
}
|
||||
|
||||
func tsconfigJSON() string {
|
||||
return `{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
func nodeIndexTS() string {
|
||||
// Template literal backtick handled via concatenation
|
||||
return "import http from 'http';\n\n" +
|
||||
"const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;\n\n" +
|
||||
"const server = http.createServer((_req, res) => {\n" +
|
||||
" res.writeHead(200, { 'Content-Type': 'text/plain' });\n" +
|
||||
" res.end('Hello World\\n');\n" +
|
||||
"});\n\n" +
|
||||
"server.listen(port, () => {\n" +
|
||||
" console.log(`Server running on port ${port}`);\n" +
|
||||
"});\n"
|
||||
}
|
||||
|
||||
func nodeDockerfile() string {
|
||||
return `FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
EXPOSE 3000
|
||||
CMD ["node", "dist/index.js"]
|
||||
`
|
||||
}
|
||||
|
||||
func pythonApp() string {
|
||||
return `from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Hello World"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
`
|
||||
}
|
||||
|
||||
func pythonDockerfile() string {
|
||||
return `FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY . .
|
||||
EXPOSE 8000
|
||||
CMD ["python", "app.py"]
|
||||
`
|
||||
}
|
||||
|
||||
func goMod(name string) string {
|
||||
return fmt.Sprintf("module %s\n\ngo 1.22\n", name)
|
||||
}
|
||||
|
||||
func goMain() string {
|
||||
return `package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "Hello World")
|
||||
})
|
||||
fmt.Println("Listening on :" + port)
|
||||
if err := http.ListenAndServe(":"+port, nil); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
func goDockerfile() string {
|
||||
return `FROM golang:1.22-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY go.* ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 go build -o app .
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /app/app /app
|
||||
EXPOSE 8080
|
||||
CMD ["/app"]
|
||||
`
|
||||
}
|
||||
|
||||
func staticHTML(name string) string {
|
||||
return fmt.Sprintf(`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>%s</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>%s</h1>
|
||||
</body>
|
||||
</html>
|
||||
`, name, name)
|
||||
}
|
||||
|
||||
func staticDockerfile() string {
|
||||
return `FROM nginx:alpine
|
||||
COPY . /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
`
|
||||
}
|
||||
Reference in New Issue
Block a user