Files
opencode-skill/skills/website-creator/scripts/install-tina-backend.sh
Kunthawat Greethong 628298183a feat: migrate website-creator from Next.js+Payload to Astro+Tina CMS
Major changes:
- Replace Payload CMS with Tina CMS (self-hosted)
- Add Astro DB for consent logging (PDPA compliant)
- Update Tailwind v3 to v4 (@tailwindcss/vite plugin)
- Add astro-tina-starter template
- Rewrite consent template for Astro (ConsentBanner.astro, Astro DB, Nano Stores)
- Add install-tina-backend.sh for self-hosted Tina per customer
- Rename convert-astro.sh to migrate-tina.sh
- Add AGENTS.md template for generated websites
- Delete all Payload/Next.js files

Technical updates:
- Astro DB using defineDb with eq operators for queries
- Tailwind v4 with @theme block
- Tina CMS local development mode
- Proper Astro API routes for consent

Research-verified with official documentation (April 2026)
2026-04-17 14:52:59 +07:00

327 lines
8.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
BACKEND_PATH="${1:-./tina-backend}"
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
print_usage() {
cat << EOF
Usage: $(basename "$0") [target-path]
Install Tina CMS Backend (self-hosted)
Arguments:
target-path Path where Tina backend will be installed (default: ./tina-backend)
Examples:
$(basename "$0") /opt/tina-backend
This script installs a self-hosted Tina CMS backend with:
- Auth.js authentication
- SQLite database adapter
- Git provider for content
- Next.js API routes
Requirements:
- Node.js 18+
- npm or yarn
- git
EOF
}
main() {
echo "=============================================="
echo " Tina CMS Backend Installer (Self-Hosted)"
echo "=============================================="
echo ""
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
print_usage
exit 0
fi
if [ -d "$BACKEND_PATH" ]; then
log_error "Directory already exists: $BACKEND_PATH"
exit 1
fi
log_info "Creating Tina backend at: $BACKEND_PATH"
mkdir -p "$BACKEND_PATH"
cd "$BACKEND_PATH"
log_info "Creating package.json..."
cat > package.json << 'PKGEOF'
{
"name": "tina-backend",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "tinacms dev",
"build": "tinacms build",
"start": "tinacms start"
},
"dependencies": {
"@auth/core": "^0.34.0",
"@auth/drizzle-adapter": "^1.4.0",
"@libsql/client": "^0.14.0",
"@tinacms/auth": "^2.0.0",
"@tinacms/database": "^2.0.0",
"@tinacms/git-provider": "^2.0.0",
"@tinacms/graphql": "^2.0.0",
"@tinacms/mssql": "^2.0.0",
"@tinacms/server": "^2.0.0",
"drizzle-orm": "^0.38.0",
"next": "^14.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"tinacms": "^2.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}
PKGEOF
log_info "Creating TypeScript config..."
cat > tsconfig.json << 'TSEOF'
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src",
"jsx": "react-jsx"
},
"include": ["src/**/*", "tina/**/*"],
"exclude": ["node_modules"]
}
TSEOF
mkdir -p src/app/api/auth/\[...nextauth\]
mkdir -p src/app/api/tina/\[\[...tina\]\]
mkdir -p tina
log_info "Creating Auth.js configuration..."
cat > src/auth.config.ts << 'AUTHEOF'
import { AuthConfig } from "@auth/core/types";
const authConfig: AuthConfig = {
secret: process.env.NEXTAUTH_SECRET || "your-secret-change-in-production",
providers: [
{
id: "github",
name: "GitHub",
type: "oauth",
clientId: process.env.GITHUB_ID || "",
clientSecret: process.env.GITHUB_SECRET || "",
},
],
callbacks: {
async session({ session, token }) {
if (session.user && token.sub) {
session.user.email = token.email as string;
}
return session;
},
},
pages: {
signIn: "/auth/signin",
},
};
export default authConfig;
AUTHEOF
log_info "Creating NextAuth API route..."
cat > 'src/app/api/auth/[...nextauth]/route.ts' << 'NEXTAUTHEOF'
import { NextRequest, NextResponse } from "next/server";
import { AuthHandler } from "@auth/core";
import authConfig from "../../../auth.config";
const authHandler = (req: NextRequest) =>
AuthHandler({
...authConfig,
req: req as any,
resolve(): Promise<any> {
throw new Error("Function not implemented.");
},
secret: authConfig.secret!,
trustHost: true,
});
export { authHandler as GET, authHandler as POST };
NEXTAUTHEOF
log_info "Creating Tina API route..."
cat > 'src/app/api/tina/[[...tina]]/route.ts' << 'TINAEOF'
import { TinaNodeBackend } from "@tinacms/server";
import authConfig from "../../../../auth.config";
import { branchName } from "./branch";
const tinaBackend = TinaNodeBackend({
authConfig: authConfig as any,
branch: branchName,
});
export { tinaBackend as GET, tinaBackend as POST };
TINAEOF
log_info "Creating Tina branch configuration..."
cat > src/app/api/tina/branch.ts << 'BRANCHEMAP'
export const branchName = process.env.TINA_BRANCH || "main";
BRANCHEMAP
log_info "Creating database schema..."
mkdir -p src/lib
cat > src/lib/schema.ts << 'DBEOF'
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", {
id: text("id").primaryKey(),
name: text("name"),
email: text("email").unique(),
emailVerified: integer("email_verified", { mode: "boolean" }),
image: text("image"),
createdAt: integer("created_at", { mode: "timestamp" }),
updatedAt: integer("updated_at", { mode: "timestamp" }),
});
export const accounts = sqliteTable("accounts", {
id: text("id").primaryKey(),
userId: text("user_id")
.references(() => users.id)
.notNull(),
type: text("type").notNull(),
provider: text("provider").notNull(),
providerAccountId: text("provider_account_id").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
});
export const sessions = sqliteTable("sessions", {
id: text("id").primaryKey(),
sessionToken: text("session_token").unique(),
userId: text("user_id")
.references(() => users.id)
.notNull(),
expires: integer("expires", { mode: "timestamp" }).notNull(),
});
export const verificationTokens = sqliteTable("verification_tokens", {
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: integer("expires", { mode: "timestamp" }).notNull(),
});
DBEOF
log_info "Creating database client..."
cat > src/lib/db.ts << 'DBCEOF'
import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import * as schema from "./schema";
const client = createClient({
url: process.env.DATABASE_URL || "file:local.db",
});
export const db = drizzle(client, { schema });
DBCEOF
log_info "Creating environment template..."
cat > .env.example << 'ENVEOF'
NEXTAUTH_SECRET=generate-a-random-secret-here
NEXTAUTH_URL=http://localhost:3000
GITHUB_ID=your-github-oauth-app-client-id
GITHUB_SECRET=your-github-oauth-app-client-secret
DATABASE_URL=file:local.db
TINA_BRANCH=main
ENVEOF
log_info "Creating README..."
cat > README.md << 'READMEEOF'
# Tina CMS Backend (Self-Hosted)
Self-hosted Tina CMS backend with Auth.js authentication and SQLite database.
## Setup
1. Install dependencies:
```bash
npm install
```
2. Configure environment:
```bash
cp .env.example .env
# Edit .env with your settings
```
3. Set up GitHub OAuth App:
- Go to https://github.com/settings/developers
- Create a new OAuth App
- Set callback URL to: `http://your-domain.com/api/auth/callback/github`
4. Start development:
```bash
npm run dev
```
## Environment Variables
| Variable | Description |
|----------|-------------|
| NEXTAUTH_SECRET | Random secret for NextAuth |
| NEXTAUTH_URL | Your site URL |
| GITHUB_ID | GitHub OAuth Client ID |
| GITHUB_SECRET | GitHub OAuth Client Secret |
| DATABASE_URL | SQLite database path |
| TINA_BRANCH | Git branch for content |
## Connecting Frontend
In your Astro frontend's `tina/config.ts`:
```ts
import { defineConfig } from "tinacms";
export default defineConfig({
apiUrl: "https://your-tina-backend.com",
contentApiUrl: "https://your-tina-backend.com",
});
```
READMEEOF
log_success "Tina backend created at: $BACKEND_PATH"
echo ""
echo "Next steps:"
echo " 1. cd $BACKEND_PATH"
echo " 2. npm install"
echo " 3. cp .env.example .env"
echo " 4. Configure GitHub OAuth App"
echo " 5. npm run dev"
echo ""
}
main "$@"