Merge pull request 'changed app to use json config for pipeline steps' (#1) from chore/code-refactor into main

Reviewed-on: https://codeberg.org/headshed/infctl-cli/pulls/1
This commit is contained in:
Jon Brookes 2025-07-14 17:49:27 +02:00
commit 506142ccd7
49 changed files with 2059 additions and 101 deletions

16
.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
config.json
app.db
base.json
infctl-cli
config.json.1
my_log.txt
.aws_creds.yaml
*.suspect
*.deleted
*.orig
scripts/galene/groups/
scripts/galene/data/
.envrc
.vscode
*.json
bin

219
README.md
View file

@ -1,11 +1,12 @@
# INFCTL CLI
A command-line tool for automated deployment and management of an MVK (Minimal Viable Kubernetes) infrastrucure. The CLI orchestrates Kubernetes deployments by executing shell scripts and applying Kubernetes manifests through a pipeline-based approach.
A command-line tool for automated deployment and management of an MVK (Minimal Viable Kubernetes) infrastrucure. The CLI orchestrates Kubernetes deployments by executing shell scripts and applying Kubernetes manifests through a JSON-defined pipeline approach.
## Table of Contents
- [Overview](#overview)
- [Features](#features)
- [Design Philosophy](#design-philosophy)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Configuration](#configuration)
@ -20,27 +21,40 @@ A command-line tool for automated deployment and management of an MVK (Minimal V
## Overview
INFCTL CLI is a Go-based deployment orchestrator that automates the setup and deployment of INFCTL applications in Kubernetes environments. The tool executes a series of predefined scripts from the `scripts/` directory and applies Kubernetes manifests from the `k8s-manifests/` directory using kubectl and kustomize.
INFCTL CLI is a Go-based deployment orchestrator that automates the setup and deployment of INFCTL applications in Kubernetes environments. The tool executes a series of predefined scripts from the `scripts/` directory and applies Kubernetes manifests from the `k8s-manifests/` directory using kubectl and kustomize, all defined in a JSON pipeline file.
## Features
- **Automated Pipeline Execution**: Runs deployment scripts and manifests in a specific order
- **JSON-Defined Pipeline Execution**: Runs deployment scripts and manifests in an order specified in a JSON pipeline file
- **Script Orchestration**: Executes shell scripts from the `scripts/` directory for various setup tasks
- **Kustomize Integration**: Applies Kubernetes manifests using kubectl kustomize
- **Multi-Component Deployment**: Supports INFCTL, Galene, and SL application deployments
- **Namespace Management**: Automatically creates required Kubernetes namespaces
- **Secret Management**: Automated creation of secrets for databases, SMTP, AWS, etc.
- **ConfigMap Management**: Creates and manages application configuration maps
- **Infrastructure Setup**: Installs and configures cert-manager, Traefik, Longhorn, and PostgreSQL operators
- **Retry Logic**: Built-in retry mechanism for failed operations
- **Structured Logging**: JSON-based logging with debug support
- **Integrated Testing**: Includes smoke tests using k3d for validation
## Design Philosophy
infctl-cli is built on principles derived from over 20 years of experience tackling deployment and orchestration challenges. The design is inspired by a "plugin" mentality, where each plugin is essentially a script. This approach emphasizes simplicity and modularity, allowing each script to act as an independent unit of execution.
Key principles include:
- **Script-Based Orchestration**: Each script or program, when executed, returns an exit code that indicates success or failure. This exit code is used to determine the next steps in the pipeline, enabling robust and predictable orchestration.
- **Structured Logging**: Scripts produce structured logs that can be consumed by web interfaces or stored in a database. This ensures transparency and traceability, making it easier to debug and monitor deployments.
- **Modularity and Reusability**: By treating scripts as plugins, the system encourages reusability and flexibility. New functionality can be added by simply introducing new scripts without altering the core logic.
- **UNIX Philosophy**: The design adheres to the UNIX philosophy of building small, composable tools that do one thing well. Each script is a self-contained unit that performs a specific task.
This philosophy underpins infctl's ability to orchestrate complex deployments while remaining simple and extensible.
## Prerequisites
- Go 1.23.3 or later
- `DEPLOYMENT_TYPE` environment variable set to one of: `customer_k8s_setup`, `infctl_k8s_setup`, or `sl_k8s_setup`
- Kubernetes cluster with kubectl configured (for K8s deployment types)
- Bash shell environment
- For running tests: k3d installed
- Kubernetes cluster with kubectl configured (for actual deployments)
- Required Kubernetes operators (installed by the tool for K8s deployments):
- cert-manager
- Traefik ingress controller
@ -49,6 +63,37 @@ INFCTL CLI is a Go-based deployment orchestrator that automates the setup and de
## Installation
### Option 1: Download Pre-built Binary
You can run an install from `curl -L https://codeberg.org/headshed/infctl-cli/raw/branch/chore/code-refactor/install.sh | bash `
.or.
manually download the pre-built binary for your platform from the [releases page](https://codeberg.org/headshed/infctl-cli/releases).
1. Download the binary for your platform:
- **Linux**:
```bash
wget https://codeberg.org/headshed/infctl-cli/releases/download/v0.0.1/infctl-linux-amd64 -O /usr/local/bin/infctl
```
- **Windows**:
Download the `.exe` file from the [releases page](https://codeberg.org/headshed/infctl-cli/releases) and place it in a directory included in your `PATH`.
- **macOS (Intel)**:
```bash
wget https://codeberg.org/headshed/infctl-cli/releases/download/v0.0.1/infctl-darwin-amd64 -O /usr/local/bin/infctl
```
- **macOS (Apple Silicon)**:
```bash
wget https://codeberg.org/headshed/infctl-cli/releases/download/v0.0.1/infctl-darwin-arm64 -O /usr/local/bin/infctl
```
2. Make the binary executable:
```bash
chmod +x /usr/local/bin/infctl
```
### Option 2: Clone Repository and Build Binary
1. Clone the repository:
```bash
git clone <repository-url>
@ -61,28 +106,27 @@ go mod download
go build -o infctl-cli .
```
3. (Optional) Install globally:
```bash
sudo mv infctl-cli /usr/local/bin/
```
### Quick start example
4. Quick start example:
```bash
# Copy configuration examples
cp base.json.example base.json
cp config.json.example config.json
cp pipeline.json.example pipeline.json
# Edit configuration files as needed
# nano base.json
# nano config.json
# Edit* configuration files as needed
# vim base.json
# vim config.json
# vim pipeline.json
# - where vim may be your default or chosen editor be it nano, vi, ed, emacs, etc ...
# Run with desired deployment type
DEPLOYMENT_TYPE=customer_k8s_setup ./infctl-cli
# Run with pipeline file
./infctl-cli --deployment-file pipeline.json
```
## Configuration
The infctl-cli requires two configuration files:
The infctl-cli requires three configuration files:
### Base Configuration (`base.json`)
@ -114,77 +158,59 @@ Key configuration options:
- `static_url`: Static content URL
- `port`: Service port
### Pipeline Configuration (`pipeline.json`)
Copy and customize the pipeline definition:
```bash
cp pipeline.json.example pipeline.json
```
This file defines the sequence of operations to be executed, including:
- Scripts to run
- Kubernetes manifests to apply
- Order of operations
- Specific deployment type configuration
## Usage
The infctl-cli requires the `DEPLOYMENT_TYPE` environment variable to be set to specify the type of deployment. Run the CLI from a directory containing your configuration files.
### Available Deployment Types
#### 1. Customer Kubernetes Setup
For customer-specific Kubernetes deployments:
Run the CLI by providing a path to your pipeline JSON file:
```bash
DEPLOYMENT_TYPE=customer_k8s_setup ./infctl-cli
```
This deployment type:
- Creates customer-specific Kubernetes resources
- Sets up namespaces, secrets, and config maps
- Deploys customer applications with custom configurations
#### 2. INFCTL Kubernetes Setup
For INFCTL infrastructure and application deployment:
```bash
DEPLOYMENT_TYPE=inf_k8s_setup ./infctl-cli
```
This deployment type:
- Sets up core INFCTL infrastructure
- Installs required operators (cert-manager, Traefik, PostgreSQL)
- Deploys INFCTL applications and services
#### 3. SL Kubernetes Setup
For SL (Share Lite) application deployment:
```bash
DEPLOYMENT_TYPE=sl_k8s_setup ./infctl-cli
```
This deployment type:
- Deploys SL applications using Helm charts
- Configures SL-specific resources and networking
- Sets up SL instance management
### Running from Source
You can also run directly with Go:
```bash
# For Docker Compose setup
DEPLOYMENT_TYPE=customer_compose_setup go run main.go
# For customer Kubernetes setup
DEPLOYMENT_TYPE=customer_k8s_setup go run main.go
# For INFCTL Kubernetes setup
DEPLOYMENT_TYPE=inf_k8s_setup go run main.go
# For SL Kubernetes setup
DEPLOYMENT_TYPE=sl_k8s_setup go run main.go
./infctl-cli --deployment-file /path/to/pipeline.json
```
The tool will automatically:
1. Load base and customer configurations
2. Initialize SQLite database for state management
3. Execute the appropriate deployment pipeline based on `DEPLOYMENT_TYPE`
3. Execute the deployment pipeline defined in your JSON file
4. Run scripts from the `scripts/` directory
5. Apply Kubernetes manifests using kustomize (for K8s deployments)
### Running from Source
You can also run directly with Go:
```bash
go run main.go -pipeline /path/to/pipeline.json
```
### Running Tests
The project includes smoke tests using k3d for validation:
```bash
# Run all tests
go test ./... -v
# Run specific test
go test ./app -run TestRunPipeline
```
## Pipeline Execution
The CLI executes deployment tasks in two main pipelines:
The CLI executes deployment tasks defined in your pipeline.json file, which typically includes:
### Infrastructure Setup Pipeline
@ -248,15 +274,6 @@ The `k8s-manifests/` directory contains Kubernetes resources applied via kustomi
- `issuer.yaml` - Certificate issuer
- `kustomization.yaml` - Kustomize configuration
### Galene (`k8s-manifests/galene/`)
- WebRTC conferencing application manifests
- TCP/UDP ingress configurations
- Certificate management
### SL Applications (`k8s-manifests/sl/`)
- Helm chart templates for SL instances
- `Chart.yaml` and `values.yaml` for Helm deployments
## Project Structure
```
@ -266,25 +283,21 @@ infctl-cli/
├── base.json.example # Base configuration template
├── config.json.example # Customer configuration template
├── app/ # Core application logic
│ ├── app.go # Pipeline orchestration and state management
│ └── k8s.go # Kubernetes operations (kubectl, kustomize)
│ ├── app.go # Pipeline orchestration and state management
│ └── k8s.go # Kubernetes operations (kubectl, kustomize)
├── config/ # Configuration management
│ ├── base.go # Base configuration handling
│ └── customer.go # Customer configuration handling
│ ├── base.go # Base configuration handling
│ └── customer.go # Customer configuration handling
├── database/ # SQLite database operations
├── scripts/ # Shell scripts executed by the CLI
│ ├── install_*.sh # Infrastructure installation scripts
│ ├── create_*_secrets.sh # Secret creation scripts
│ ├── create_*_configmap_*.sh # ConfigMap creation scripts
│ └── galene/ # Galene-specific scripts
├── k8s-manifests/ # Kubernetes manifests applied via kustomize
│ ├── inf/ # INFCTL application manifests
│ ├── inf-ingress/ # INFCTL ingress configuration
│ ├── galene/ # Galene application manifests
│ ├── sl/ # SL application Helm charts
│ └── sl-certificate/ # SSL certificate management
│ ├── install_*.sh # Infrastructure installation scripts
│ ├── create_*_secrets.sh # Secret creation scripts
│ └── create_*_configmap_*.sh # ConfigMap creation scripts
├── k8s-manifests/ # Kubernetes manifests applied via kustomize
│ ├── ctl/ # INFCTL application manifests
│ └── ctl-ingress/ # INFCTL ingress configuration
├── templates/ # Template files for configuration generation
└── files/ # Static configuration files
└── files/ # Static configuration files
```
## Development
@ -302,15 +315,16 @@ The CLI uses structured JSON logging. Debug logs are enabled by default and incl
### Adding New Scripts
1. Place shell scripts in the `scripts/` directory
2. Add script execution to the appropriate pipeline in `app/app.go`
3. Use the `k8sRunSriptCommand()` function to execute scripts
1. Place shell scripts / executables in the `scripts/` directory
2. Add confiiguration as appropriate into `pipeline.json`
3. Re-run `cnfctl --deployment-file pipeline.json`
### Adding New Manifests
1. Create Kubernetes YAML files in the appropriate `k8s-manifests/` subdirectory
2. Include a `kustomization.yaml` file for kustomize processing
3. Add manifest application to the pipeline using `k8sRunKustomizeCommand()`
3. Add confiiguration as appropriate into `pipeline.json`
4. Re-run `cnfctl --deployment-file pipeline.json`
## Contributing
@ -323,3 +337,6 @@ The CLI uses structured JSON logging. Debug logs are enabled by default and incl
## License
This project is licensed under the GNU General Public License v3.0. See the [LICENSE](./LICENSE) file for details.
## License
This project is licensed under the GNU General Public License v3.0. See the [LICENSE](./LICENSE) file for details.

225
app/app.go Normal file
View file

@ -0,0 +1,225 @@
package app
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"log/slog"
"os"
"time"
"headshed/infctl-cli/config"
"headshed/infctl-cli/database"
)
type AppState struct {
Config config.BaseConfig
Customer config.CustomerConfig
DB *sql.DB
}
type PipelineStep struct {
Name string
Function string // Name of the function to call
Params []string // Parameters for the function
RetryCount int
ShouldAbort bool
}
var functionMap = map[string]func([]string) error{
"k8sNamespaceExists": func(params []string) error {
if len(params) != 1 {
return fmt.Errorf("invalid parameters for k8sNamespaceExists")
}
return k8sNamespaceExists(params[0])
},
"RunCommand": func(params []string) error {
if len(params) != 1 {
return fmt.Errorf("invalid parameters for RunCommand")
}
return RunCommand(params[0])
},
// Add more functions as needed
}
func parseStepsFromJSON(filePath string) ([]PipelineStep, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("failed to read JSON file: %w", err)
}
var steps []PipelineStep
if err := json.Unmarshal(data, &steps); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
return steps, nil
}
func (app *AppState) ToDoDeployment() []PipelineStep {
slog.Info("ToDo deployment is not implemented yet")
return []PipelineStep{}
}
func (app *AppState) RunJsonDeployment() []PipelineStep {
jsonFile := app.Config.DeploymentFile
if jsonFile == "" {
log.Fatal("no config specified with --deployment-file=<path_to_config_file>")
}
file, err := os.Open(jsonFile)
if err != nil {
slog.Error(fmt.Sprintf("Failed to open JSON file: %s", err))
}
defer file.Close()
steps, err := parseStepsFromJSON(jsonFile)
if err != nil {
slog.Error(fmt.Sprintf("Failed to parse JSON file: %s", err))
}
for _, step := range steps {
slog.Info(fmt.Sprintf("🔄 Running step: %s", step.Name))
function, exists := functionMap[step.Function]
if !exists {
slog.Error(fmt.Sprintf("Unknown function: %s", step.Function))
continue
}
err := function(step.Params)
if err != nil {
slog.Error(fmt.Sprintf("❌ Step failed: %s, error: %v", step.Name, err))
if step.ShouldAbort {
log.Fatalf("🚨Critical failure at step: %s", step.Name)
}
} else {
slog.Info(fmt.Sprintf("✅ Step completed: %s", step.Name))
}
}
return steps
}
func (app *AppState) getPipeline() []PipelineStep {
switch app.Config.DeploymentType {
case "api":
return app.ToDoDeployment()
case "json":
return app.RunJsonDeployment()
default:
return app.RunJsonDeployment()
}
}
func NewAppState(cust config.CustomerConfig, config config.BaseConfig, dbPath string) (*AppState, error) {
db, err := database.NewDatabase(dbPath)
if err != nil {
return nil, err
}
return &AppState{
Config: config,
Customer: cust,
DB: db,
}, nil
}
func (app *AppState) runPipeline(steps []PipelineStep) error {
for _, step := range steps {
slog.Info(fmt.Sprintf("🔄 Running step: %s\n", step.Name))
// Look up the function in the functionMap
function, exists := functionMap[step.Function]
if !exists {
slog.Error(fmt.Sprintf("❌ Unknown function: %s", step.Function))
if step.ShouldAbort {
return fmt.Errorf("🚨critical failure: unknown function %s", step.Function)
}
continue
}
// Execute the function with the provided parameters
err := function(step.Params)
if err != nil {
slog.Error(fmt.Sprintf("❌ Step failed: %s, error: %v", step.Name, err))
// Retry logic
if step.RetryCount > 0 {
for i := 0; i < step.RetryCount; i++ {
slog.Info("Waiting for 20 seconds before retrying...")
time.Sleep(20 * time.Second)
if innerErr := function(step.Params); innerErr == nil {
slog.Info(fmt.Sprintf("✅ Step completed: %s\n", step.Name))
err = nil
break
} else {
err = innerErr
}
}
}
// Handle failure after retries
if err != nil {
if step.ShouldAbort {
return fmt.Errorf("🚨critical failure at step: %s", step.Name)
}
continue
}
}
slog.Info(fmt.Sprintf("✅ Step completed: %s\n", step.Name))
}
return nil
}
func (app *AppState) SetUpNewCustomer() error {
/*
| --------------------------
| main pipeline
| --------------------------
*/
steps := app.getPipeline()
app.runPipeline(steps)
slog.Info(fmt.Sprintln("🎉 Customer setup complete!"))
return nil
}
func (app *AppState) CreatePipeline() error {
isNew, err := database.CheckProjectName(app.DB, app.Customer.Project)
if err != nil {
return fmt.Errorf("failed to check project name: %w", err)
}
if isNew {
port, err := database.GetNextPortNumber(app.DB)
if err != nil {
return fmt.Errorf("failed to get next port number: %w", err)
}
err = database.AddProjectName(app.DB, app.Customer.Project, port)
if err != nil {
return fmt.Errorf("failed to add project name: %w", err)
}
slog.Info(fmt.Sprintln("Project name added:", app.Customer.Project))
fmt.Printf("Port number assigned: %d\n", port)
app.Config.Port = port
} else {
slog.Info(fmt.Sprintln("Project name already exists:", app.Customer.Project))
}
err = app.SetUpNewCustomer()
if err != nil {
return fmt.Errorf("failed to set up new customer: %w", err)
}
return nil
}

158
app/app_test.go Normal file
View file

@ -0,0 +1,158 @@
package app
import (
"encoding/json"
"math/rand"
"os"
"os/exec"
"path/filepath"
"testing"
"headshed/infctl-cli/config"
)
func TestMain(m *testing.M) {
// Setup: Set TEST_ENV=true for all tests
err := os.Setenv("TEST_ENV", "true")
if err != nil {
panic("Failed to set TEST_ENV")
}
// Run all tests
code := m.Run()
// Teardown: Unset TEST_ENV after all tests
os.Unsetenv("TEST_ENV")
// Exit with the test result code
os.Exit(code)
}
func TestRunPipeline(t *testing.T) {
// Create a temporary directory for test assets
tempDir, err := os.MkdirTemp("", "smoke-test")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir) // Cleanup after test
// Create test scripts
scripts := map[string]string{
"good.sh": "#!/bin/bash\necho 'Good script executed'\nexit 0",
"warning.sh": "#!/bin/bash\necho 'Warning script executed'\nexit 0",
"error.sh": "#!/bin/bash\necho 'Error script executed'\nexit 1",
}
for name, content := range scripts {
scriptPath := filepath.Join(tempDir, name)
if err := os.WriteFile(scriptPath, []byte(content), 0755); err != nil {
t.Fatalf("Failed to create script %s: %v", name, err)
}
}
// Create a test JSON pipeline file
pipeline := []PipelineStep{
{Name: "Good Step", Function: "RunCommand", Params: []string{filepath.Join(tempDir, "good.sh")}, RetryCount: 0, ShouldAbort: false},
{Name: "Warning Step", Function: "RunCommand", Params: []string{filepath.Join(tempDir, "warning.sh")}, RetryCount: 0, ShouldAbort: false},
{Name: "Error Step", Function: "RunCommand", Params: []string{filepath.Join(tempDir, "error.sh")}, RetryCount: 0, ShouldAbort: true},
}
pipelineFile := filepath.Join(tempDir, "pipeline.json")
pipelineData, err := json.Marshal(pipeline)
if err != nil {
t.Fatalf("Failed to marshal pipeline: %v", err)
}
if err := os.WriteFile(pipelineFile, pipelineData, 0644); err != nil {
t.Fatalf("Failed to write pipeline file: %v", err)
}
// Set up AppState
app := &AppState{
Config: config.BaseConfig{
DeploymentFile: pipelineFile,
},
}
// Run the pipeline
err = app.runPipeline(pipeline)
if err == nil {
t.Errorf("Expected error due to 'Error Step', but got none")
}
}
func randomString(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, length)
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
return string(b)
}
func TestK3DNamespaceCreation(t *testing.T) {
// Check if k3d is installed
_, err := exec.LookPath("k3d")
if err != nil {
t.Fatal("k3d is not installed. Please install k3d to run this test.")
}
// Create a test cluster
clusterName := "test-" + randomString(6)
cmd := exec.Command("k3d", "cluster", "create", clusterName, "--servers", "1")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Fatalf("Failed to create k3d cluster: %v", err)
}
defer func() {
// Clean up the test cluster
cmd := exec.Command("k3d", "cluster", "delete", clusterName)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Errorf("Failed to delete k3d cluster: %v", err)
}
}()
// Create a temporary directory for the pipeline config
tempDir, err := os.MkdirTemp("", "k3d-test")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir)
// Create a test JSON pipeline file
pipeline := []PipelineStep{
{Name: "Ensure Namespace Exists", Function: "k8sNamespaceExists", Params: []string{"test-namespace"}, RetryCount: 0, ShouldAbort: true},
}
pipelineFile := filepath.Join(tempDir, "pipeline.json")
pipelineData, err := json.Marshal(pipeline)
if err != nil {
t.Fatalf("Failed to marshal pipeline: %v", err)
}
if err := os.WriteFile(pipelineFile, pipelineData, 0644); err != nil {
t.Fatalf("Failed to write pipeline file: %v", err)
}
// Set up AppState
app := &AppState{
Config: config.BaseConfig{
DeploymentFile: pipelineFile,
},
}
// Run the pipeline
err = app.runPipeline(pipeline)
if err != nil {
t.Fatalf("Pipeline execution failed: %v", err)
}
// Verify the namespace exists
cmd = exec.Command("kubectl", "get", "ns", "test-namespace")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Fatalf("Namespace 'test-namespace' was not created: %v", err)
}
}

85
app/k8s.go Normal file
View file

@ -0,0 +1,85 @@
package app
import (
"bytes"
"fmt"
"log/slog"
"os/exec"
"strings"
)
func k8sNamespaceExists(project string) error {
commandString := "kubectl get ns " + project
cmd := exec.Command("sh", "-c", commandString)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
if strings.Contains(stderr.String(), "not found") {
err := k8sCreateNamespace(project)
if err != nil {
slog.Error(fmt.Sprintf("Failed to create namespace: %s", project))
return fmt.Errorf("failed to create namespace: %w", err)
}
return nil
} else {
slog.Error(fmt.Sprintf("❌ Command failed with error: %v\n", err))
slog.Debug(fmt.Sprintf("🐞 Stdout: %s\n", stdout.String()))
slog.Debug(fmt.Sprintf("🐞 Stderr: %s\n", stderr.String()))
return fmt.Errorf("failed to run kubectl command: %w", err)
}
}
output := stdout.String()
if !strings.Contains(output, project) {
return fmt.Errorf("namespace %s does not exist", project)
}
slog.Info(fmt.Sprintf("k8sNamespaceExists nothing to do - project: %s eists ...", project))
return nil
}
func k8sCreateNamespace(project string) error {
slog.Info(fmt.Sprintf("in k8sCreateNamespace with project: %s", project))
commandString := "kubectl create ns " + project
cmd := exec.Command("sh", "-c", commandString)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
slog.Error(fmt.Sprintf("❌ Command failed with error: %v\n", err))
slog.Debug(fmt.Sprintf("🐞 Stdout: %s\n", stdout.String()))
slog.Debug(fmt.Sprintf("🐞 Stderr: %s\n", stderr.String()))
return fmt.Errorf("failed to run kubectl command: %w", err)
}
output := stdout.String()
if !strings.Contains(output, project) {
return fmt.Errorf("failed to create namespace %s", project)
}
return nil
}
func RunCommand(command string) error {
slog.Debug(fmt.Sprintf("🐞 Running script command: %s", command))
cmd := exec.Command("sh", "-c", command)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
slog.Error(fmt.Sprintf("❌ Command failed with error: %v\n", err))
slog.Debug(fmt.Sprintf("🐞 Stdout: %s\n", stdout.String()))
slog.Debug(fmt.Sprintf("🐞 Stderr: %s\n", stderr.String()))
return fmt.Errorf("failed to run script command: %w", err)
}
output := stdout.String()
slog.Debug(fmt.Sprintf("RunCommand command executed successfully: %s", command))
slog.Debug(fmt.Sprintf("RunCommand command output: %s", output))
return nil
}

17
base.json.example Normal file
View file

@ -0,0 +1,17 @@
{
"projects_directory": "/home/user/docker/",
"app_image": "headshead/some-app:0.0.1",
"webserver_image": "headsheddev/some-webserver_image:0.0.1",
"db": "database/database.sqlite",
"env": ".env",
"preview_path": "prv",
"data_www": "data_www",
"static_images": "data_images/",
"public_images": "images",
"php_conf": "php/local.ini",
"exports": "exports",
"logs": "logs/laravel.log",
"admin_url": ".headshed.dev",
"preview_url": "-prv.headshed.dev",
"nginx_conf": "nginx/conf.d"
}

17
build.sh Executable file
View file

@ -0,0 +1,17 @@
#!/bin/bash
mkdir -p bin
echo "Building for Linux..."
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/infctl-linux-amd64
echo "Building for Windows..."
GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o bin/infctl-windows-amd64.exe
echo "Building for macOS (Intel)..."
GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o bin/infctl-darwin-amd64
echo "Building for macOS (Apple Silicon)..."
GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o bin/infctl-darwin-arm64
echo "Build complete. Binaries are in the 'bin' directory."

19
config.json.example Normal file
View file

@ -0,0 +1,19 @@
{
"project": "hdshd",
"project_data": "path_to/project_data",
"app_image": "headsheddev/some-app:0.0.1",
"env": "path_to/.env",
"images": "path_to_images",
"php_conf": "path_to/local.ini",
"exports": "path_to/exports",
"db": "path_to/db",
"logs": "path_to/logs",
"preview_path": "path_to_preview",
"webserver_image": "headsheddev/my-nginx:0.0.1",
"public_images": "path_to/images",
"data_www": "path_to/www",
"nginx_conf": "path_to/conf.d",
"admin_url": "admin_url.headshed.dev",
"preview_url": "app-prv.headshed.dev",
"ui_url": "ww2.headshed.dev",
}

63
config/base.go Normal file
View file

@ -0,0 +1,63 @@
package config
import (
"encoding/json"
"flag"
"fmt"
"os"
)
type BaseConfig struct {
ProjectsDirectory string `json:"projects_directory"`
Env string `json:"env"`
StaticImages string `json:"static_images"`
PublicImages string `json:"public_images"`
PhpConf string `json:"php_conf"`
Exports string `json:"exports"`
Logs string `json:"logs"`
PreviewPath string `json:"preview_path"`
DataWww string `json:"data_www"`
NginxConf string `json:"nginx_conf"`
AdminURL string `json:"admin_url"`
PreviewURL string `json:"preview_url"`
AppImage string `json:"app_image"`
WebserverImage string `json:"webserver_image"`
EmptyDB string `json:"empty_db"`
DB string `json:"db"`
EmptyImages string `json:"empty_imaages"`
DeploymentType string `json:"deployment_type"`
DeploymentFile string `json:"deployment_file"`
Port int `json:"port"`
}
func ReadBaseConfig(path string) (BaseConfig, error) {
deploymentType := os.Getenv("DEPLOYMENT_TYPE")
deploymentFile := flag.String("deployment-file", "", "path to config file")
helpFlag := flag.Bool("help", false, "show help")
flag.Parse()
if *helpFlag {
fmt.Println("Usage: infctl-cli --deployment-file=<path_to_config_file>")
fmt.Println("DEPLOYMENT_TYPE environment variable must be set to 'json' or 'api (api is not implemented yet)'")
os.Exit(0)
}
var config BaseConfig
if *deploymentFile != "" {
config.DeploymentFile = *deploymentFile
}
data, err := os.ReadFile(path)
if err != nil {
return BaseConfig{}, fmt.Errorf("failed to read file: %w", err)
}
if err := json.Unmarshal(data, &config); err != nil {
return BaseConfig{}, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
config.DeploymentType = deploymentType
return config, nil
}

29
config/customer.go Normal file
View file

@ -0,0 +1,29 @@
package config
import (
"encoding/json"
"fmt"
"os"
)
type CustomerConfig struct {
Project string `json:"project"`
CustomerDirectory string `json:"customer_directory"`
UIURL string `json:"ui_url"`
StaticURL string `json:"static_url"`
Port int `json:"port"`
}
func ReadCustomerConfig(path string) (CustomerConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return CustomerConfig{}, fmt.Errorf("failed to read file: %w", err)
}
var cust CustomerConfig
if err := json.Unmarshal(data, &cust); err != nil {
return CustomerConfig{}, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
return cust, nil
}

74
database/database.go Normal file
View file

@ -0,0 +1,74 @@
package database
import (
"database/sql"
"log"
"log/slog"
"os"
_ "modernc.org/sqlite"
)
func NewDatabase(dbPath string) (*sql.DB, error) {
// Check if the application is running in a test environment
if os.Getenv("TEST_ENV") == "true" {
dbPath = ":memory:" // Use in-memory database for tests
slog.Info("🧪 Running in test environment, using in-memory database")
log.Fatal("🧪 Running in test environment, using in-memory database ")
}
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return nil, err
}
createTableSQL := `
CREATE TABLE IF NOT EXISTS project_name (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_name TEXT NOT NULL,
port INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
_, err = db.Exec(createTableSQL)
if err != nil {
return nil, err
}
return db, nil
}
func CheckProjectName(db *sql.DB, projectName string) (bool, error) {
var exists bool
query := `SELECT EXISTS(SELECT 1 FROM project_name WHERE project_name = ? LIMIT 1);`
err := db.QueryRow(query, projectName).Scan(&exists)
if err != nil && err != sql.ErrNoRows {
return false, err
}
return !exists, nil
}
func AddProjectName(db *sql.DB, projectName string, port int) error {
query := `INSERT INTO project_name (project_name, port) VALUES (?, ?);`
_, err := db.Exec(query, projectName, port)
if err != nil {
return err
}
return nil
}
func GetNextPortNumber(db *sql.DB) (int, error) {
var maxPortNumber sql.NullInt64
query := `SELECT MAX(port) FROM project_name;`
err := db.QueryRow(query).Scan(&maxPortNumber)
if err != nil && err != sql.ErrNoRows {
return 0, err
}
if !maxPortNumber.Valid {
// No rows in the table, return a default port number
return 10000, nil
}
return int(maxPortNumber.Int64 + 1), nil
}

View file

@ -0,0 +1,30 @@
server {
listen 80;
# Redirect all HTTP requests to HTTPS
# return 301 https://$host$request_uri;
index index.php index.html;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /var/www/public;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass localhost:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param HTTPS on;
fastcgi_param HTTP_X_FORWARDED_PROTO https;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
gzip_static on;
}
location /storage/ {
alias /var/www/storage/app/public/;
autoindex on;
}
}

8
files/ctl/php/local.ini Normal file
View file

@ -0,0 +1,8 @@
; PHP Configuration
; extension=pdo_pgsql.so
; extension=pgsql.so
upload_max_filesize = 100M
post_max_size = 100M
max_execution_time = 300
memory_limit = 256M
; Add any other PHP configurations you need

18
go.mod Normal file
View file

@ -0,0 +1,18 @@
module headshed/infctl-cli
go 1.23.3
require modernc.org/sqlite v1.38.0
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/sys v0.33.0 // indirect
modernc.org/libc v1.65.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)

47
go.sum Normal file
View file

@ -0,0 +1,47 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

37
install.sh Executable file
View file

@ -0,0 +1,37 @@
#!/bin/bash
set -e
# Determine the platform
OS=$(uname -s)
ARCH=$(uname -m)
# Map architecture
if [ "$ARCH" == "x86_64" ]; then
ARCH="amd64"
elif [[ "$ARCH" == "arm64" || "$ARCH" == "aarch64" ]]; then
ARCH="arm64"
else
echo "Unsupported architecture: $ARCH"
exit 1
fi
# Map OS
case "$OS" in
Linux) OS="linux" ;;
Darwin) OS="darwin" ;;
*) echo "Unsupported OS: $OS"; exit 1 ;;
esac
# Construct the download URL
VERSION="v0.0.1"
BINARY_URL="https://codeberg.org/headshed/infctl-cli/releases/download/$VERSION/infctl-$OS-$ARCH"
# Download the binary
echo "Downloading infctl binary for $OS-$ARCH..."
sudo curl -s -L "$BINARY_URL" -o /usr/local/bin/infctl
# Make it executable
sudo chmod +x /usr/local/bin/infctl
echo "infctl installed successfully!"

View file

@ -0,0 +1,24 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-infctl-ingress-http
namespace: infctl
annotations:
cert-manager.io/issuer: "le-cluster-issuer-http"
kubernetes.io/ingress.class: "traefik"
spec:
tls:
- hosts:
- ctl.headshed.dev
secretName: tls-infctl-ingress-http
rules:
- host: ctl.headshed.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: infctl-service
port:
name: web

View file

@ -0,0 +1,22 @@
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: le-cluster-issuer-http
namespace: infctl
spec:
acme:
email: marshyon@gmail.com
# We use the staging server here for testing to avoid hitting
# server: https://acme-staging-v02.api.letsencrypt.org/directory
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: http-issuer-account-key
# solvers:
# - http01:
# # The ingressClass used to create the necessary ingress routes
# ingress:
# class: traefik
solvers:
- http01:
ingress:
class: traefik

View file

@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- issuer.yaml
- service.yaml
- ingress.yaml

View file

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: infctl-service
namespace: infctl
spec:
selector:
app: nginx-sl
ports:
- name: web
protocol: TCP
port: 80
targetPort: 80

View file

@ -0,0 +1,298 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: infctl-deployment
namespace: infctl
labels:
app: nginx-sl
spec:
replicas: 1
selector:
matchLabels:
app: nginx-sl
template:
metadata:
labels:
app: nginx-sl
spec:
imagePullSecrets:
- name: registry-credentials
initContainers:
- name: init-data-s3
image: amazon/aws-cli:latest
imagePullPolicy: IfNotPresent
# command: ["/bin/bash", "-c", "trap : TERM INT; sleep infinity & wait"]
# command: ["/bin/bash", "-c", "/scripts/init-data.sh && echo 'Script completed, sleeping for debugging' && sleep 3600"]
command: ["/bin/bash", "/scripts/init-data.sh"]
volumeMounts:
- name: init-script
mountPath: /scripts
- name: infctl-public-data
mountPath: /var/www/public
- name: infctl-storage-data
mountPath: /var/www/storage
- name: infctl-database-data
mountPath: /var/www/database
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-credentials
key: access-key
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-credentials
key: secret-key
- name: AWS_DEFAULT_REGION
value: "eu-west-2"
- name: merge-data
image: busybox
imagePullPolicy: IfNotPresent
# command: ["/bin/sh", "-c", "trap : TERM INT; sleep infinity & wait"]
command: ["/bin/sh", "/scripts/merge_data_inf.sh"]
volumeMounts:
- name: merge-script
mountPath: /scripts
- name: infctl-public-data
mountPath: /var/www/public
- name: infctl-storage-data
mountPath: /var/www/storage
- name: infctl-database-data
mountPath: /var/www/database
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-credentials
key: access-key
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-credentials
key: secret-key
- name: AWS_DEFAULT_REGION
value: "eu-west-2"
containers:
- name: php-fpm
image: $APP_CONTAINER
command: ["/bin/sh", "-c"]
args:
- |
cd /var/www
php artisan config:clear
npm install && npm run build
php-fpm
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
env:
- name: APP_NAME
value: "infctl"
- name: APP_ENV
value: production
- name: APP_KEY
valueFrom:
secretKeyRef:
name: app-key-secret
key: app_key
- name: APP_DEBUG
value: "false"
- name: APP_URL
value: "https://ctl.headshed.dev/"
- name: APP_LOCAL
value: "en"
- name: APP_FALLBACK_LOCALE
value: "en"
- name: APP_FAKER_LOCALE
value: "en_US"
- name: APP_MAINTENANCE_DRIVER
value: "file"
- name: PHP_CLI_SERVER_WORKERS
value: "4"
- name: BCRYPT_ROUNDS
value: "12"
- name: LOG_CHANNEL
value: "stack"
- name: LOG_STACK
value: "single"
- name: LOG_DEPRECATIONS_CHANNEL
value: ""
- name: LOG_LEVEL
value: "debug"
- name: DB_CONNECTION
value: pgsql
- name: DB_HOST
valueFrom:
secretKeyRef:
name: pg-credentials
key: host
- name: DB_PORT
value: "5432"
- name: DB_DATABASE
valueFrom:
secretKeyRef:
name: pg-credentials
key: dbname
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: pg-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: pg-credentials
key: password
- name: SESSION_DRIVER
value: "database"
- name: SESSION_LIFETIME
value: "120"
- name: SESSION_ENCRYPT
value: "false"
- name: SESSION_PATH
value: "/"
- name: SESSION_DOMAIN
value: ""
- name: BROADCAST_CONNECTION
value: "log"
- name: FILESYSTEM_DISK
value: "s3"
- name: FILAMENT_FILESYSTEM_DISK
value: "s3"
- name: QUEUE_CONNECTION
value: "redis"
- name: CACHE_STORE
value: "database"
- name: MEMCACHED_HOST
value: "127.0.0.1"
- name: REDIS_CLIENT
value: "phpredis"
- name: REDIS_HOST
value: "redis.redis.svc.cluster.local"
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis-auth
key: password
- name: REDIS_PORT
value: "6379"
- name: MAIL_MAILER
value: "smtp"
- name: MAIL_HOST
value: "smtp.fastmail.com"
- name: MAIL_PORT
value: "465"
- name: MAIL_USERNAME
valueFrom:
secretKeyRef:
name: smtp-credentials
key: user
- name: MAIL_PASSWORD
valueFrom:
secretKeyRef:
name: smtp-credentials
key: password
- name: MAIL_ENCRYPTION
value: "ssl"
- name: MAIL_FROM_ADDRESS
value: "info@headshed.dev"
- name: MAIL_FROM_NAME
value: "Headshed"
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-credentials
key: access-key
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-credentials
key: secret-key
- name: AWS_DEFAULT_REGION
value: "eu-west-2"
- name: AWS_BUCKET
value: "headsheddev-sharelt-cust-data"
- name: AWS_USE_PATH_STYLE_ENDPOINT
value: "false"
- name: VITE_APP_NAME
value: "infctl"
tty: true
workingDir: /var/www
volumeMounts:
- name: infctl-public-data
mountPath: /var/www/public
- name: infctl-storage-data
mountPath: /var/www/storage
- name: infctl-database-data
mountPath: /var/www/database
- mountPath: /usr/local/etc/php/conf.d/local.ini
name: php-config
subPath: local.ini
- name: nginx
image: nginx:1.28
imagePullPolicy: IfNotPresent
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
ports:
- containerPort: 80
volumeMounts:
- name: infctl-public-data
mountPath: /var/www/public
- name: infctl-storage-data
mountPath: /var/www/storage
- name: infctl-database-data
mountPath: /var/www/database
- name: nginx-config
mountPath: /etc/nginx/conf.d
volumes:
- name: init-script
configMap:
name: init-data-script
- name: merge-script
configMap:
name: merge-data-script
- name: nginx-config
configMap:
name: nginx-config
# - name: nginx-content
# configMap:
# name: nginx-content
- name: infctl-public-data
persistentVolumeClaim:
claimName: infctl-public-data-pvc
- name: infctl-storage-data
persistentVolumeClaim:
claimName: infctl-storage-data-pvc
- name: infctl-database-data
persistentVolumeClaim:
claimName: infctl-database-data-pvc
- name: php-config
configMap:
name: php-config

View file

@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- pvc.yaml
- deployment.yaml

View file

@ -0,0 +1,35 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: infctl-public-data-pvc
namespace: infctl
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: infctl-storage-data-pvc
namespace: infctl
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: infctl-database-data-pvc
namespace: infctl
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

71
main.go Normal file
View file

@ -0,0 +1,71 @@
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"fmt"
"log"
"log/slog"
"os"
"headshed/infctl-cli/app"
"headshed/infctl-cli/config"
)
func main() {
var levelVar slog.LevelVar
levelVar.Set(slog.LevelDebug)
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: &levelVar}))
slog.SetDefault(logger)
if err := run(); err != nil {
log.Fatalf("Application error: %v", err)
}
}
func run() error {
wd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current directory: %w", err)
}
baseConfigPath := wd + string(os.PathSeparator) + "base.json"
configPath := wd + string(os.PathSeparator) + "config.json"
baseConfig, err := config.ReadBaseConfig(baseConfigPath)
if err != nil {
return fmt.Errorf("error reading base config file: %w", err)
}
customerConfig, err := config.ReadCustomerConfig(configPath)
if err != nil {
return fmt.Errorf("error reading customer config file: %w", err)
}
appState, err := app.NewAppState(customerConfig, baseConfig, "app.db")
if err != nil {
return fmt.Errorf("failed to initialize app state: %w", err)
}
defer func() {
if err := appState.DB.Close(); err != nil {
log.Printf("Error closing database: %v", err)
}
}()
if err := appState.CreatePipeline(); err != nil {
return fmt.Errorf("failed to create customer project: %w", err)
}
return nil
}

16
pipeline.json.example Normal file
View file

@ -0,0 +1,16 @@
[
{
"name": "ensure inf namespace exists",
"function": "k8sNamespaceExists",
"params": ["infctl"],
"retryCount": 0,
"shouldAbort": true
},
{
"name": "run inf redis secret",
"function": "RunCommand",
"params": ["./scripts/create_php_configmap_ctl.sh"],
"retryCount": 0,
"shouldAbort": true
}
]

View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
output=$(kubectl -n postgres-operator get pods --selector=postgres-operator.crunchydata.com/control-plane=postgres-operator --field-selector=status.phase=Running 2>&1)
if echo "$output" | grep -iq 'running'; then
echo "At least one pod is running."
else
echo "No running pods found."
exit 1
fi

View file

@ -0,0 +1,28 @@
#!/usr/bin/env bash
if kubectl get secret app-key-secret -n infctl >/dev/null 2>&1; then
echo "Secret app-key-secret already exists in namespace infctl. Exiting."
exit 0
fi
generate_app_key() {
APP_KEY=$(docker run --rm \
--entrypoint /bin/sh \
$APP_CONTAINER \
-c "cd /var/www && \
cp .env.example .env && \
php artisan key:generate --force > /dev/null 2>&1 && \
grep 'APP_KEY' .env | sed 's/APP_KEY=//'")
APP_KEY=$(echo "$APP_KEY" | tr -d '\r\n')
}
generate_app_key
echo "Extracted APP_KEY: $APP_KEY"
kubectl create secret generic app-key-secret \
--from-literal=app_key="$APP_KEY" \
-n infctl --dry-run=client -o yaml | kubectl apply -f -

16
scripts/create_aws_secrets.sh Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
echo ""
echo ""
temp_file=$(mktemp)
kubectl -n infctl create secret generic aws-credentials -o yaml --dry-run=client \
--from-literal access-key=$AWS_ACCESS_KEY_ID \
--from-literal secret-key=$AWS_SECRET_ACCESS_KEY > "$temp_file"
kubectl apply -f $temp_file
rm $temp_file

View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
if kubectl -n cert-manager get secret cloudflare-api-token-secret &>/dev/null; then
echo "Secret 'cloudflare-api-token-secret' already exists in 'cert-manager' namespace. Skipping."
exit 0
fi
kubectl create secret generic cloudflare-api-token-secret --from-literal=api-token=$API_TOKEN --namespace='cert-manager'

View file

@ -0,0 +1,15 @@
#!/usr/bin/env bash
NS="postgres-operator"
USER=$(kubectl -n $NS get secrets ctl-pguser-ctl-controller -o jsonpath='{.data.user}' | base64 -d)
PASSWORD=$(kubectl -n $NS get secrets ctl-pguser-ctl-controller -o jsonpath='{.data.password}' | base64 -d)
HOST=$(kubectl -n $NS get secrets ctl-pguser-ctl-controller -o jsonpath='{.data.host}' | base64 -d)
PORT=$(kubectl -n $NS get secrets ctl-pguser-ctl-controller -o jsonpath='{.data.port}' | base64 -d)
DBNAME=$(kubectl -n $NS get secrets ctl-pguser-ctl-controller -o jsonpath='{.data.dbname}' | base64 -d)
PG_URI=$(kubectl -n $NS get secrets ctl-pguser-ctl-controller -o jsonpath='{.data.uri}' | base64 -d)
SECRET_YAML=$(kubectl -n infctl create secret generic pg-credentials -o yaml --dry-run=client --from-literal=username="$USER" --from-literal=password="$PASSWORD" --from-literal=host="$HOST" --from-literal=dbname="$DBNAME")
echo "$SECRET_YAML" | kubectl apply -f -

8
scripts/create_crunchy_db.sh Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
EXAMOPLES_DIR=/home/user/projects/crunchy/postgres-operator-examples
cd $EXAMOPLES_DIR # || echo "Directory $EXAMOPLES_DIR does not exist" && exit 1
kubectl apply -k kustomize/postgres

View file

@ -0,0 +1,12 @@
#!/usr/bin/env bash
EXAMOPLES_DIR=/home/user/projects/crunchy/postgres-operator-examples
cd $EXAMOPLES_DIR # || echo "Directory $EXAMOPLES_DIR does not exist" && exit 1
pwd
# exit 1
kubectl apply -k kustomize/install/namespace
kubectl apply --server-side -k kustomize/install/default

View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
SCRIPT=scripts/init-data-ctl.sh
CONFIGMAP=$(kubectl -n infctl create configmap init-data-script --from-file=init-data.sh=$SCRIPT --dry-run=client -o yaml)
echo "$CONFIGMAP" | kubectl apply -f -

View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
CREATE_CONFIGMAP=$(kubectl -n infctl create configmap merge-data-script --from-file=scripts/merge_data_ctl.sh --dry-run=client -o yaml)
echo $CREATE_CONFIGMAP
echo "$CREATE_CONFIGMAP" | kubectl -n infctl apply -f -
if [ $? -ne 0 ]; then
echo "Failed to create or update the configmap."
exit 1
fi

View file

@ -0,0 +1,15 @@
#!/usr/bin/env bash
NGINX_CONFIGMAP=$(kubectl -n infctl create configmap nginx-config --from-file files/ctl/nginx/default.conf --dry-run=client -oyaml)
if [ -z "$NGINX_CONFIGMAP" ]; then
echo "Failed to create NGINX configmap."
exit 1
fi
echo "$NGINX_CONFIGMAP" | kubectl apply -f -
if [ $? -ne 0 ]; then
echo "Failed to apply NGINX configmap."
exit 1
fi
echo "NGINX configmap created successfully."

14
scripts/create_pg_ctl.sh Normal file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
cat <<EOF | kubectl apply -f -
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-cluster
namespace: pg-cluster
spec:
instances: 2
storage:
size: 2Gi
EOF

21
scripts/create_pg_secrets.sh Executable file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env bash
NS="pg-cluster"
USERNAME=$(kubectl -n $NS get secrets pg-cluster-app -o jsonpath='{.data.username}' | base64 -d)
PASSWORD=$(kubectl -n $NS get secrets pg-cluster-app -o jsonpath='{.data.password}' | base64 -d)
HOST=$(kubectl -n $NS get secrets pg-cluster-app -o jsonpath='{.data.host}' | base64 -d)
PORT=$(kubectl -n $NS get secrets pg-cluster-app -o jsonpath='{.data.port}' | base64 -d)
DBNAME=$(kubectl -n $NS get secrets pg-cluster-app -o jsonpath='{.data.dbname}' | base64 -d)
PG_URI=$(kubectl -n $NS get secrets pg-cluster-app -o jsonpath='{.data.uri}' | base64 -d)
postgres_fqdn="${HOST}.${NS}.svc.cluster.local"
echo ""
echo "this script needs to be sourced"
echo "then run a command to use it like "
echo ""
echo 'kubectl -n infctl create secret generic pg-credentials -o yaml --dry-run=client --from-literal username=$USERNAME --from-literal password=$PASSWORD --from-literal host=$postgres_fqdn --from-literal dbname=$DBNAME'

View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
NGINX_CONFIGMAP=$(kubectl -n infctl create configmap php-config --from-file files/ctl/php/local.ini --dry-run=client -oyaml)
if [ -z "$NGINX_CONFIGMAP" ]; then
echo "Failed to create NGINX configmap."
exit 1
fi
echo "$NGINX_CONFIGMAP" | kubectl apply -f -
if [ $? -ne 0 ]; then
echo "Failed to apply NGINX configmap."
exit 1
fi
echo "NGINX configmap created successfully."

5
scripts/create_redis_secret.sh Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
kubectl -n infctl delete secrets redis-auth
kubectl get secret redis-auth -n redis -o yaml | sed "s/namespace: redis/namespace: infctl/" | kubectl apply -n infctl -f -

View file

@ -0,0 +1,24 @@
#!/usr/bin/env bash
if kubectl get secret registry-credentials -n infctl >/dev/null 2>&1; then
echo "Secret 'registry-credentials' already exists in namespace 'infctl'. Skipping."
exit 0
fi
echo "Container Registry Server: $SERVER"
kubectl create secret docker-registry registry-credentials \
--docker-server=$SERVER \
--docker-username=$USER \
--docker-password=$PASSWORD \
--docker-email=$EMAIL \
-n infctl
if [ $? -ne 0 ]; then
echo "Error: Failed to create the docker-registry secret."
exit 1
fi
echo "Docker registry secret created successfully."

View file

@ -0,0 +1,6 @@
#!/usr/bin/env bash
CREATE_SMTP_CREDS=$(kubectl -n infctl create secret generic smtp-credentials -o yaml --dry-run=client --from-literal user=$SMTP_USER --from-literal password=$SMTP_PASS)
echo "$CREATE_SMTP_CREDS" | kubectl apply -f -

42
scripts/init-data-ctl.sh Normal file
View file

@ -0,0 +1,42 @@
LOG_FILE="/var/log/init-data.log"
mkdir -p /var/log
echo "env variables" | tee -a "$LOG_FILE"
env | tee -a "$LOG_FILE"
ls -lirt /var/www/public | tee -a "$LOG_FILE"
mkdir -p /var/www/{public,storage,database}
# Function to log errors and continue
log_error() {
echo "[ERROR] $1" | tee -a "$LOG_FILE"
}
# Check if public directory is empty
if [ -z "$(find /var/www/public -type f -o -type d -not -name "lost+found" -not -path "/var/www/public" 2>/dev/null)" ]; then
echo "Public directory is empty, copying data from S3..." | tee -a "$LOG_FILE"
aws s3 cp $S3_BUCKET/assets/public.tar /var/www/public/ 2>>"$LOG_FILE" || log_error "Failed to copy public data from S3"
else
echo "Public directory already has data, skipping S3 copy..." | tee -a "$LOG_FILE"
fi
# Check if storage directory is empty
if [ -z "$(find /var/www/storage -type f -o -type d -not -name "lost+found" -not -path "/var/www/storage" 2>/dev/null)" ]; then
echo "Storage directory is empty, copying data from S3..." | tee -a "$LOG_FILE"
aws s3 cp $S3_BUCKET/assets/storage.tar /var/www/storage/ 2>>"$LOG_FILE" || log_error "Failed to copy storage data from S3"
else
echo "Storage directory already has data, skipping S3 copy..." | tee -a "$LOG_FILE"
fi
# Check if database directory is empty
if [ -z "$(ls -A /var/www/database 2>/dev/null)" ]; then
echo "Database directory is empty, copying data from S3..." | tee -a "$LOG_FILE"
aws s3 cp $S3_BUCKET/assets/database.tar /var/www/database/ 2>>"$LOG_FILE" || log_error "Failed to copy database data from S3"
else
echo "Database directory already has data, skipping S3 copy..." | tee -a "$LOG_FILE"
fi
echo "Script completed. Check $LOG_FILE for details."

11
scripts/install_cert-manager.sh Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
if kubectl -n cert-manager get pods 2>/dev/null | grep -q 'Running'; then
echo "cert-manager pods already running. Skipping installation."
exit 0
fi
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml

View file

@ -0,0 +1,37 @@
#!/usr/bin/env bash
if kubectl -n cnpg-system get pods | grep cnpg &>/dev/null; then
echo "CloudNativePG pods already running. Skipping installation."
exit 0
fi
echo "Installing CloudNativePG..."
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm upgrade --install cnpg \
--namespace cnpg-system \
--set config.clusterWide=true \
--skip-crds \
--force \
cnpg/cloudnative-pg
# had to do this
# kubectl get mutatingwebhookconfiguration,validatingwebhookconfiguration,crd -A | grep cnpg
# Delete Conflicting Resources (if safe to do so):
# kubtctl delete <anyting in the above list>
# kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.17/releases/cnpg-1.17.5.yaml
# kubectl patch configmap cnpg-config -n cnpg-system --type merge -p '{"data":{"config":"clusterWide: true"}}'

9
scripts/install_longhorn.sh Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Check if there are any pods in the longhorn-system namespace
if kubectl -n longhorn-system get pods --no-headers 2>/dev/null | grep -q '^[^ ]'; then
echo "Pods already exist in the longhorn-system namespace. Skipping installation."
exit 0
fi
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.8.1/deploy/longhorn.yaml

168
scripts/install_traefik.sh Executable file
View file

@ -0,0 +1,168 @@
#!/usr/bin/env bash
if kubectl -n traefik get pods --no-headers 2>/dev/null | grep -q 'Running'; then
echo "Traefik is already running in the 'traefik' namespace. Upgrading instead."
# Create a temporary values file for more complex configuration
cat > /tmp/traefik-values.yaml <<EOF
ports:
web:
port: 80
websecure:
port: 443
traefik:
port: 9000
turn-tcp:
port: 1194
exposedPort: 1194
protocol: TCP
turn-udp:
port: 1194
exposedPort: 1194
protocol: UDP
entryPoints:
turn-tcp:
address: ":1194/tcp"
turn-udp:
address: ":1194/udp"
api:
dashboard: true
insecure: true
ingressRoute:
dashboard:
enabled: true
ping: true
log:
level: INFO
# Add this service section to expose the ports properly
service:
enabled: true
type: LoadBalancer
annotations: {}
ports:
web:
port: 80
protocol: TCP
targetPort: web
websecure:
port: 443
protocol: TCP
targetPort: websecure
turn-tcp:
port: 1194
protocol: TCP
targetPort: turn-tcp
turn-udp:
port: 1194
protocol: UDP
targetPort: turn-udp
EOF
helm upgrade traefik traefik/traefik --namespace traefik -f /tmp/traefik-values.yaml
else
echo "Installing Traefik..."
helm repo add traefik https://traefik.github.io/charts
helm repo update
# Create a temporary values file for more complex configuration
cat > /tmp/traefik-values.yaml <<EOF
ports:
web:
port: 80
websecure:
port: 443
traefik:
port: 9000
turn-tcp:
port: 1194
exposedPort: 1194
protocol: TCP
turn-udp:
port: 1194
exposedPort: 1194
protocol: UDP
entryPoints:
turn-tcp:
address: ":1194/tcp"
turn-udp:
address: ":1194/udp"
api:
dashboard: true
insecure: true
ingressRoute:
dashboard:
enabled: true
ping: true
log:
level: INFO
# Add the service section here too for new installations
service:
enabled: true
type: LoadBalancer
annotations: {}
ports:
web:
port: 80
protocol: TCP
targetPort: web
websecure:
port: 443
protocol: TCP
targetPort: websecure
turn-tcp:
port: 1194
protocol: TCP
targetPort: turn-tcp
turn-udp:
port: 1194
protocol: UDP
targetPort: turn-udp
EOF
helm install traefik traefik/traefik --namespace traefik --create-namespace -f /tmp/traefik-values.yaml
fi
cat > traefik-turn-service.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: traefik-turn
namespace: traefik
labels:
app.kubernetes.io/instance: traefik-traefik
app.kubernetes.io/name: traefik
spec:
type: LoadBalancer
ports:
- name: turn-tcp
port: 1194
protocol: TCP
targetPort: turn-tcp
- name: turn-udp
port: 1194
protocol: UDP
targetPort: turn-udp
selector:
app.kubernetes.io/instance: traefik-traefik
app.kubernetes.io/name: traefik
EOF
kubectl apply -f traefik-turn-service.yaml
rm -f traefik-turn-service.yaml
echo "Don't forget to create TCP and UDP ingress routes for the TURN server with:"
echo "kubectl apply -f k8s-manifests/galene/ingressroute-tcp.yaml"
echo "kubectl apply -f k8s-manifests/galene/ingressroute-udp.yaml"
echo ""
echo "To access the dashboard:"
echo "kubectl port-forward -n traefik \$(kubectl get pods -n traefik -l \"app.kubernetes.io/name=traefik\" -o name) 9000:9000"
echo "Then visit http://localhost:9000/dashboard/ in your browser"

30
scripts/redis_secret.sh Executable file
View file

@ -0,0 +1,30 @@
#!/usr/bin/env bash
NAMESPACE=redis
REDIS_SECRET=redis-auth
generate_password() {
pwgen 32 1
}
REDIS_PASSWORD=$(generate_password)
NAMESPACE_EXISTS=$(kubectl get namespace $NAMESPACE --ignore-not-found)
if [ -z "$NAMESPACE_EXISTS" ]; then
echo "Creating namespace $NAMESPACE"
kubectl create namespace $NAMESPACE
else
echo "Namespace $NAMESPACE already exists"
fi
REDIS_SECRET_EXISTS=$(kubectl get secret $REDIS_SECRET -n $NAMESPACE --ignore-not-found)
if [ -z "$REDIS_SECRET_EXISTS" ]; then
echo "Creating secret $REDIS_SECRET in namespace $NAMESPACE"
kubectl create secret generic $REDIS_SECRET -n $NAMESPACE \
--from-literal=password=$REDIS_PASSWORD \
--dry-run=client -o yaml | kubectl apply -f -
else
echo "Secret $REDIS_SECRET already exists in namespace $NAMESPACE"
fi

63
templates/env.tmpl Normal file
View file

@ -0,0 +1,63 @@
APP_NAME=Laravel
APP_ENV=local
APP_DEBUG=false
APP_URL=http://{{- .AppURL }}
BEANSTALKD_API="api:8080/api/v1/job"
BEANSTALKD_PREVIEW_PAYLOAD="PostPreviewJob-{{- .Project }}"
BEANSTALKD_PUBLISH_PAYLOAD="PostPublishJob-{{- .Project }}"
APP_PREVIEW_URL=https://{{- .Preview }}
STATIC_URL=https://{{- .Static }}
APP_USER_INTERFACE_URL={{- .UI }}
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=database
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
OCTANE_SERVER=frankenphp

30
templates/nginx_app.conf Normal file
View file

@ -0,0 +1,30 @@
server {
listen {{ .Port }};
# Redirect all HTTP requests to HTTPS
# return 301 https://$host$request_uri;
index index.php index.html;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /var/www/public;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param HTTPS on;
fastcgi_param HTTP_X_FORWARDED_PROTO https;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
gzip_static on;
}
location /storage/ {
alias /var/www/storage/app/public/;
autoindex on;
}
}