2 Komitmen 04a771665e ... 4db9bb3727

Pembuat SHA1 Pesan Tanggal
  Ben 4db9bb3727 Update wrapper 4 hari lalu
  Ben 04a771665e Update wrapper 1 Minggu lalu
12 mengubah file dengan 464 tambahan dan 32 penghapusan
  1. 0 0
      cmd/main.go
  2. 21 1
      go.mod
  3. 40 0
      go.sum
  4. TEMPAT SAMPAH
      main
  5. 17 0
      pkg/job/job.go
  6. 7 6
      pkg/symbols/symbols.go
  7. 139 0
      pkg/workflow/workflow.go
  8. 76 11
      tools/aws.go
  9. 0 13
      tools/cloudformation.go
  10. 6 1
      tools/docker.go
  11. 53 0
      tools/file.go
  12. 105 0
      tools/ssl.go

+ 0 - 0
main.go → cmd/main.go


+ 21 - 1
go.mod

@@ -4,11 +4,16 @@ go 1.25.0
 
 require (
 	dario.cat/mergo v1.0.2
+	github.com/aws/aws-sdk-go-v2 v1.41.2
+	github.com/aws/aws-sdk-go-v2/config v1.32.10
+	github.com/aws/aws-sdk-go-v2/service/ec2 v1.291.0
+	github.com/aws/aws-sdk-go-v2/service/s3 v1.96.1
 	github.com/go-git/go-git/v6 v6.0.0-20260222090600-424e9964d3a3
 	github.com/google/uuid v1.6.0
 	github.com/hashicorp/go-version v1.8.0
 	github.com/hashicorp/hc-install v0.9.3
 	github.com/hashicorp/terraform-exec v0.25.0
+	github.com/hashicorp/terraform-json v0.27.2
 	github.com/moby/go-archive v0.2.0
 	github.com/moby/moby/api v1.53.0
 	github.com/moby/moby/client v0.2.2
@@ -26,6 +31,22 @@ require (
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/ProtonMail/go-crypto v1.3.0 // indirect
 	github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
+	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
+	github.com/aws/aws-sdk-go-v2/credentials v1.19.10 // indirect
+	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 // indirect
+	github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
+	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
+	github.com/aws/smithy-go v1.24.1 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 	github.com/cloudflare/circl v1.6.3 // indirect
 	github.com/containerd/errdefs v1.0.0 // indirect
@@ -44,7 +65,6 @@ require (
 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 	github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
-	github.com/hashicorp/terraform-json v0.27.2 // indirect
 	github.com/kevinburke/ssh_config v1.6.0 // indirect
 	github.com/klauspost/compress v1.18.4 // indirect
 	github.com/klauspost/cpuid/v2 v2.3.0 // indirect

+ 40 - 0
go.sum

@@ -14,6 +14,46 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
 github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
+github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
+github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
+github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
+github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
+github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=
+github.com/aws/aws-sdk-go-v2/service/ec2 v1.291.0 h1:E0/zdPeHKCpXVRAImhnHJYgpfZnTCjnr6i75gZIhwHs=
+github.com/aws/aws-sdk-go-v2/service/ec2 v1.291.0/go.mod h1:2dMnUs1QzlGzsm46i9oBHAxVHQp7b6qF7PljWcgVEVE=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9 h1:IJRzQTvdpjHRPItx9gzNcz7Y1F+xqAR+xiy9rr5ZYl8=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.96.1 h1:giB30dEeoar5bgDnkE0q+z7cFjcHaCjulpmPVmuKR84=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.96.1/go.mod h1:071TH4M3botFLWDbzQLfBR7tXYi7Fs2RsXSiH7nlUlY=
+github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
+github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
+github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
+github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
+github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
+github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
+github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=
+github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=

TEMPAT SAMPAH
main


+ 17 - 0
pkg/job/job.go

@@ -44,6 +44,14 @@ type Job struct {
 	i                *interp.Interpreter
 }
 
+type BlankFormatter struct{}
+
+// Format renders a log entry to only the message part.
+func (f *BlankFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+	// Return the message string as a byte slice, adding a newline character.
+	return []byte(entry.Message + "\n"), nil
+}
+
 func New() *Job {
 	j := &Job{
 		state:        state.New(),
@@ -53,6 +61,7 @@ func New() *Job {
 		workspacedir: "workspace",
 	}
 
+	j.log.Formatter = &BlankFormatter{}
 	j.log.AddHook(j)
 
 	return j
@@ -273,6 +282,14 @@ func (j *Job) Run(f string) *Job {
 		}
 	}
 
+	fmt.Println(j.module)
+	sss := j.i.Symbols("main")
+	for p, vv := range sss {
+		for k, v := range vv {
+			fmt.Println(p, k, v.Type(), v)
+		}
+	}
+
 	for k, v := range j.i.Globals() {
 		if v.CanInterface() {
 			if v.Kind() == reflect.Func {

+ 7 - 6
pkg/symbols/symbols.go

@@ -17,12 +17,13 @@ var (
 			"LookPath":       reflect.ValueOf(exec.LookPath),
 		},
 		"git.bazzel.dev/bmallen/helios/tools/tools": {
-			"Docker":         reflect.ValueOf(tools.Docker),
-			"CloudFormation": reflect.ValueOf(tools.CloudFormation),
-			"Terraform":      reflect.ValueOf(tools.Terraform),
-			"Git":            reflect.ValueOf(tools.Git),
-			"AWS":            reflect.ValueOf(tools.AWS),
-			"SetLogger":      reflect.ValueOf(tools.SetLogger),
+			"Docker":    reflect.ValueOf(tools.Docker),
+			"Terraform": reflect.ValueOf(tools.Terraform),
+			"Git":       reflect.ValueOf(tools.Git),
+			"AWS":       reflect.ValueOf(tools.AWS),
+			"SSL":       reflect.ValueOf(tools.SSL),
+			"File":      reflect.ValueOf(tools.File),
+			"SetLogger": reflect.ValueOf(tools.SetLogger),
 		},
 		"github.com/google/uuid/uuid": {
 			"New":       reflect.ValueOf(uuid.New),

+ 139 - 0
pkg/workflow/workflow.go

@@ -0,0 +1,139 @@
+package workflow
+
+import (
+	"time"
+
+	"git.bazzel.dev/bmallen/helios/pkg/state"
+	"github.com/sirupsen/logrus"
+)
+
+type (
+	Workflow struct {
+		name  string
+		jobs  jobs
+		c     Context
+		start time.Time
+		end   time.Time
+		log   *logrus.Logger
+	}
+	job struct {
+		name     string
+		steps    steps
+		needs    []string
+		workflow *Workflow
+		start    time.Time
+		end      time.Time
+	}
+	jobs      []*job
+	jobOption interface {
+		Job(j *job)
+	}
+	step struct {
+		name     string
+		f        StepFunc
+		start    time.Time
+		end      time.Time
+		workflow *Workflow
+		job      *job
+	}
+	steps []*step
+	needs struct {
+		name string
+	}
+
+	Context interface {
+		Get(key string) any
+		Set(key string, value any)
+		Delete(key string)
+		Keys() []string
+		Range(f func(key string, value any) bool)
+		ID() string
+	}
+	StepFunc func(ctx Context) error
+)
+
+func NewWorkflow(name string) *Workflow {
+	return &Workflow{
+		name: name,
+		jobs: jobs{},
+		c:    state.New(),
+		log:  logrus.New(),
+	}
+}
+
+func (w *Workflow) Run() (err error) {
+	w.start = time.Now()
+	defer func() {
+		w.end = time.Now()
+	}()
+
+	for _, j := range w.jobs {
+		j.start = time.Now()
+		w.log.Printf("# Job: %s\n", j.name)
+		for _, step := range j.steps {
+			w.log.Printf("- Step: %s / %s\n", j.name, step.name)
+			err := step.Run()
+			w.log.Printf("  Duration: %s\n", step.Duration())
+			if err != nil {
+				break
+			}
+		}
+		j.end = time.Now()
+		if err != nil {
+			break
+		}
+	}
+
+	return err
+}
+
+func (w *Workflow) Job(name string, opts ...jobOption) *Workflow {
+	job := &job{
+		name:     name,
+		steps:    steps{},
+		needs:    []string{},
+		workflow: w,
+	}
+
+	for _, opt := range opts {
+		opt.Job(job)
+	}
+
+	w.jobs = append(w.jobs, job)
+	return w
+}
+
+func Step(name string, f StepFunc) jobOption {
+	return &step{
+		name: name,
+		f:    f,
+	}
+}
+
+func (s *step) Job(j *job) {
+	s.job = j
+	s.workflow = j.workflow
+	j.steps = append(j.steps, s)
+}
+
+func (s *step) Run() error {
+	s.start = time.Now()
+	err := s.f(s.workflow.c)
+	s.end = time.Now()
+
+	return err
+}
+
+func (s *step) Duration() time.Duration {
+	return s.end.Sub(s.start)
+}
+
+func Needs(name string) jobOption {
+	return &needs{
+		name: name,
+	}
+}
+
+func (o *needs) Job(j *job) {
+	j.needs = append(j.needs, o.name)
+}

+ 76 - 11
tools/aws.go

@@ -1,27 +1,92 @@
 package tools
 
-func AWS() *aws {
-	return &aws{}
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/config"
+	"github.com/aws/aws-sdk-go-v2/credentials"
+	"github.com/aws/aws-sdk-go-v2/service/ec2/types"
+)
+
+func AWS() *awstool {
+	ctx := context.TODO()
+
+	accessKeyID := os.Getenv("AWS_ACCESS_KEY_ID")
+	secretAccessKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
+	sessionToken := os.Getenv("AWS_SESSION_TOKEN")
+
+	staticProvider := credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, sessionToken)
+
+	cfg, err := config.LoadDefaultConfig(ctx,
+		config.WithCredentialsProvider(staticProvider),
+		config.WithRegion(os.Getenv("AWS_REGION")),
+	)
+	if err != nil {
+		log.Write([]byte(fmt.Sprintf("unable to load SDK config, %v", err)))
+	}
+
+	return &awstool{
+		cfg: cfg,
+	}
 }
 
 type (
-	aws struct{}
-	s3  struct {
-		aws *aws
+	awstool struct {
+		cfg aws.Config
+	}
+	s3 struct {
+		aws *awstool
 	}
 	ec2 struct {
-		aws *aws
+		aws *awstool
+	}
+	cloudformation struct {
+		aws *awstool
+	}
+	imagebuilder struct {
+		aws *awstool
+	}
+	ecs struct {
+		aws *awstool
+	}
+	eks struct {
+		aws *awstool
 	}
 )
 
-func (t *aws) S3() *s3   { return &s3{aws: t} }
-func (t *aws) EC2() *ec2 { return &ec2{aws: t} }
+func (t *awstool) S3() *s3                         { return &s3{aws: t} }
+func (t *awstool) EC2() *ec2                       { return &ec2{aws: t} }
+func (t *awstool) CloudFormation() *cloudformation { return &cloudformation{aws: t} }
+func (t *awstool) ImageBuilder() *imagebuilder     { return &imagebuilder{aws: t} }
+func (t *awstool) ECS() *ecs                       { return &ecs{aws: t} }
+func (t *awstool) EKS() *eks                       { return &eks{aws: t} }
 
-func (t *s3) Put(src, dst string) error  { return nil }
-func (t *s3) Get(src, dst string) error  { return nil }
-func (t *s3) List(path string) error { return nil }
+func (t *awstool) Filter(filters map[string][]string) []types.Filter {
+	filter := []types.Filter{}
 
+	for k, v := range filters {
+		filter = append(filter, types.Filter{
+			Name:   aws.String(k),
+			Values: v,
+		})
+	}
+	return filter
+}
+
+func (t *s3) Put(src, dst string) error             { return nil }
+func (t *s3) Get(src, dst string) error             { return nil }
+func (t *s3) ListObjects(bucket, path string) error { return nil }
+func (t *s3) ListBuckets() error                    { return nil }
+
+func (t *ec2) FindLatestAMI() error    { return nil }
 func (t *ec2) Create() error    { return nil }
 func (t *ec2) Stop() error      { return nil }
 func (t *ec2) Start() error     { return nil }
 func (t *ec2) Terminate() error { return nil }
+
+func (t *cloudformation) CreateStack() {}
+func (t *cloudformation) UpdateStack() {}
+func (t *cloudformation) DeleteStack() {}

+ 0 - 13
tools/cloudformation.go

@@ -1,13 +0,0 @@
-package tools
-
-func CloudFormation() *cloudformation {
-	return &cloudformation{}
-}
-
-type (
-	cloudformation struct{}
-)
-
-func (t *cloudformation) CreateStack() {}
-func (t *cloudformation) UpdateStack() {}
-func (t *cloudformation) DeleteStack() {}

+ 6 - 1
tools/docker.go

@@ -156,7 +156,12 @@ func (t *dockerContainer) HostConfig() *container.HostConfig        { return t.h
 func (t *dockerContainer) NetworkConfig() *network.NetworkingConfig { return t.network }
 func (t *dockerContainer) Exec()                                    {}
 func (t *dockerContainer) Run(destOut io.Writer, destErr io.Writer, timeout time.Duration) (int64, error) {
-	err := t.Start()
+	err := t.Create()
+	if err != nil {
+		return -1, err
+	}
+
+	err = t.Start()
 	if err != nil {
 		return -1, err
 	}

+ 53 - 0
tools/file.go

@@ -0,0 +1,53 @@
+package tools
+
+import (
+	"encoding/json"
+	"os"
+
+	"gopkg.in/yaml.v3"
+)
+
+func File() *file {
+	return &file{}
+}
+
+type (
+	file struct {
+	}
+)
+
+func (t *file) WriteYaml(path string, obj any) error {
+	out, err := yaml.Marshal(obj)
+	if err != nil {
+		return err
+	}
+
+	return os.WriteFile(path, out, 0644)
+}
+
+func (t *file) WriteJSON(path string, obj any) error {
+	out, err := json.Marshal(obj)
+	if err != nil {
+		return err
+	}
+
+	return os.WriteFile(path, out, 0644)
+}
+
+func (t *file) ReadYaml(path string, obj any) error {
+	in, err := os.ReadFile(path)
+	if err != nil {
+		return err
+	}
+
+	return yaml.Unmarshal(in, obj)
+}
+
+func (t *file) ReadJSON(path string, obj any) error {
+	in, err := os.ReadFile(path)
+	if err != nil {
+		return err
+	}
+
+	return json.Unmarshal(in, obj)
+}

+ 105 - 0
tools/ssl.go

@@ -0,0 +1,105 @@
+package tools
+
+import (
+	"bytes"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"fmt"
+	"math/big"
+	"os"
+	"time"
+)
+
+func SSL() *ssl {
+	return &ssl{}
+}
+
+type (
+	ssl struct {
+	}
+	sslcert struct {
+		privateKey *rsa.PrivateKey
+		Template   x509.Certificate
+	}
+)
+
+func (t *ssl) Cert(cn string) (res *sslcert) {
+	res = &sslcert{}
+	var err error
+	
+	res.privateKey, err = rsa.GenerateKey(rand.Reader, 4096)
+	if err != nil {
+		log.Write([]byte(fmt.Sprintf("failed to generate private key: %w", err)))
+		return
+	}
+
+	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+	if err != nil {
+		log.Write([]byte(fmt.Sprintf("failed to generate serial number: %w", err)))
+		return
+	}
+
+	res.Template = x509.Certificate{
+		SerialNumber: serialNumber,
+		Subject: pkix.Name{
+			// Organization: []string{org},
+			CommonName: cn, // Essential for browsers/clients to trust the cert
+		},
+		NotBefore: time.Now(),
+		NotAfter:  time.Now().Add(365 * 24 * time.Hour), // Valid for 1 year
+
+		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
+		BasicConstraintsValid: true,
+		IsCA:                  true, // Mark as a CA to sign other certs if needed
+
+		DNSNames: []string{cn},
+	}
+
+	return
+}
+
+func (res *sslcert) WriteCSR(file string) error {
+	csrTemplate := x509.CertificateRequest{
+		Subject:  res.Template.Subject,
+		DNSNames: res.Template.DNSNames,
+	}
+
+	csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, res.privateKey)
+	if err != nil {
+		return fmt.Errorf("Failed to create certificate request: %v", err)
+	}
+
+	csrPEM := pem.EncodeToMemory(&pem.Block{
+		Type:  "CERTIFICATE REQUEST",
+		Bytes: csrBytes,
+	})
+
+	return os.WriteFile(file, csrPEM, 0644)
+}
+
+func (res *sslcert) WritePEMs(key, crt string) error {
+	certBytes, err := x509.CreateCertificate(rand.Reader, &res.Template, &res.Template, res.privateKey.PublicKey, res.privateKey)
+	if err != nil {
+		return fmt.Errorf("failed to create certificate: %w", err)
+	}
+
+	certPEM := new(bytes.Buffer)
+	pem.Encode(certPEM, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
+
+	keyPEM := new(bytes.Buffer)
+	pem.Encode(keyPEM, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(res.privateKey)})
+
+	if err := os.WriteFile(crt, certPEM.Bytes(), 0644); err != nil {
+		return fmt.Errorf("failed to write cert.pem: %w", err)
+	}
+	if err := os.WriteFile(key, keyPEM.Bytes(), 0600); err != nil {
+		return fmt.Errorf("failed to write key.pem: %w", err)
+	}
+
+	return nil
+}