From 1a2d8e281c77f3e8adcf91058b9975203bec9df2 Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Tue, 24 Jan 2023 12:46:53 +0100 Subject: [PATCH 01/11] Instance custom passwords PoC --- go.mod | 1 + go.sum | 2 ++ pkg/cfg/aem.yml | 3 +++ pkg/cfg/config.go | 21 ++++++++++++++++++++- pkg/common/cryptox/cryptox.go | 29 +++++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 pkg/common/cryptox/cryptox.go diff --git a/go.mod b/go.mod index 98dc33ab..e446d598 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,7 @@ require ( github.com/pelletier/go-toml v1.9.4 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sethvargo/go-password v0.2.0 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/go.sum b/go.sum index e96b786b..4a2c543b 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/samber/lo v1.33.0 h1:2aKucr+rQV6gHpY3bpeZu69uYoQOzVhGT3J22Op6Cjk= github.com/samber/lo v1.33.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= github.com/segmentio/textio v1.2.0 h1:Ug4IkV3kh72juJbG8azoSBlgebIbUUxVNrfFcKHfTSQ= github.com/segmentio/textio v1.2.0/go.mod h1:+Rb7v0YVODP+tK5F7FD9TCkV7gOYx9IgLHWiqtvY8ag= +github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= +github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= diff --git a/pkg/cfg/aem.yml b/pkg/cfg/aem.yml index f5a390d8..1a008ce4 100644 --- a/pkg/cfg/aem.yml +++ b/pkg/cfg/aem.yml @@ -133,6 +133,9 @@ base: # Location of temporary files (downloaded AEM packages, etc) tmp_dir: aem/home/tmp + # Salt used in encrypting sensitive values + salt: "[[BASE_SALT]]" + log: level: info timestamp_format: "2006-01-02 15:04:05" diff --git a/pkg/cfg/config.go b/pkg/cfg/config.go index 315e10aa..8eef15b1 100644 --- a/pkg/cfg/config.go +++ b/pkg/cfg/config.go @@ -4,6 +4,7 @@ import ( "bytes" _ "embed" "fmt" + "github.com/sethvargo/go-password/password" log "github.com/sirupsen/logrus" "github.com/spf13/viper" "github.com/wttech/aemc/pkg/common" @@ -125,13 +126,31 @@ func (c *Config) ConfigureLogger() { //go:embed aem.yml var configYml string +func renderConfigYml() (string, error) { + tplParsed, err := tplx.New("config-tpl").Delims("[[", "]]").Parse(configYml) + if err != nil { + return "", err + } + var tplOutput bytes.Buffer + if err := tplParsed.Execute(&tplOutput, map[string]any{ + "BASE_SALT": password.MustGenerate(16, 2, 2, false, false), + }); err != nil { + return "", err + } + return tplOutput.String(), nil +} + func (c *Config) Init() error { file := File() if pathx.Exists(file) { return fmt.Errorf("config file already exists: '%s'", file) } - err := filex.WriteString(file, configYml) + + configYmlRendered, err := renderConfigYml() if err != nil { + return fmt.Errorf("cannot render initial config file from template: '%w'", err) + } + if err = filex.WriteString(file, configYmlRendered); err != nil { return fmt.Errorf("cannot create initial config file '%s': '%w'", file, err) } return nil diff --git a/pkg/common/cryptox/cryptox.go b/pkg/common/cryptox/cryptox.go new file mode 100644 index 00000000..9a14524d --- /dev/null +++ b/pkg/common/cryptox/cryptox.go @@ -0,0 +1,29 @@ +package cryptox + +import ( + "crypto/aes" + "encoding/hex" + log "github.com/sirupsen/logrus" +) + +func Encrypt(key []byte, plaintext string) (string, error) { + c, err := aes.NewCipher(key) + if err != nil { + log.Fatalf("encryption key/salt is invalid: %s", err) + } + encrypted := make([]byte, len(plaintext)) + c.Encrypt(encrypted, []byte(plaintext)) + return hex.EncodeToString(encrypted), nil +} + +func Decrypt(key []byte, ct string) (string, error) { + c, err := aes.NewCipher(key) + if err != nil { + log.Fatalf("encryption key/salt is invalid: %s", err) + } + decoded, _ := hex.DecodeString(ct) + dest := make([]byte, len(decoded)) + c.Decrypt(dest, decoded) + s := string(dest[:]) + return s, nil +} From 7a47ef28ae1ab9a1970e45cfd9b2145ea9c014ce Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Tue, 24 Jan 2023 22:57:03 +0100 Subject: [PATCH 02/11] Oak run prototype --- .gitattributes | 11 +++ pkg/cfg/aem.yml | 7 +- pkg/common/cryptox/cryptox.go | 24 ++++--- pkg/common/osx/lock.go | 14 ++-- pkg/instance/constants.go | 3 + pkg/instance/resource/cbp.exe | Bin 15360 -> 130 bytes pkg/instance/resource/oak-run-1.46.0.jar | 3 + pkg/local_instance.go | 58 +++++++++++---- pkg/local_instance_manager.go | 8 +++ pkg/oak_run.go | 86 +++++++++++++++++++++++ pkg/package_manager.go | 8 ++- pkg/sdk.go | 12 ++-- 12 files changed, 191 insertions(+), 43 deletions(-) create mode 100644 pkg/instance/resource/oak-run-1.46.0.jar create mode 100644 pkg/oak_run.go diff --git a/.gitattributes b/.gitattributes index 43165c68..dda208fc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,14 @@ # Force UNIX line-ending to avoid errors on higher environments * text=auto eol=lf + +# Handle embedded binaries as Git LFS resources + +**/resource/*.zip filter=lfs diff=lfs merge=lfs -text +**/resource/*.tar.gz filter=lfs diff=lfs merge=lfs -text +**/resource/*.tar filter=lfs diff=lfs merge=lfs -text +**/resource/*.jar filter=lfs diff=lfs merge=lfs -text +**/resource/*.exe filter=lfs diff=lfs merge=lfs -text +**/resource/*.rpm filter=lfs diff=lfs merge=lfs -text +**/resource/*.deb filter=lfs diff=lfs merge=lfs -text +**/resource/*.so filter=lfs diff=lfs merge=lfs -text diff --git a/pkg/cfg/aem.yml b/pkg/cfg/aem.yml index 1a008ce4..0cf41298 100644 --- a/pkg/cfg/aem.yml +++ b/pkg/cfg/aem.yml @@ -75,6 +75,10 @@ instance: # Archived runtime dir (AEM backup files '*.aemb.zst') backup_dir: "aem/home/data/backup" + # Oak Run tool options (offline instance management) + oakrun: + source: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.46.0/oak-run-1.46.0.jar" + # Source files quickstart: # AEM SDK ZIP or JAR @@ -133,9 +137,6 @@ base: # Location of temporary files (downloaded AEM packages, etc) tmp_dir: aem/home/tmp - # Salt used in encrypting sensitive values - salt: "[[BASE_SALT]]" - log: level: info timestamp_format: "2006-01-02 15:04:05" diff --git a/pkg/common/cryptox/cryptox.go b/pkg/common/cryptox/cryptox.go index 9a14524d..7b5de3a3 100644 --- a/pkg/common/cryptox/cryptox.go +++ b/pkg/common/cryptox/cryptox.go @@ -2,28 +2,36 @@ package cryptox import ( "crypto/aes" + "crypto/sha256" "encoding/hex" log "github.com/sirupsen/logrus" ) -func Encrypt(key []byte, plaintext string) (string, error) { +func EncryptString(key []byte, text string) string { c, err := aes.NewCipher(key) if err != nil { log.Fatalf("encryption key/salt is invalid: %s", err) } - encrypted := make([]byte, len(plaintext)) - c.Encrypt(encrypted, []byte(plaintext)) - return hex.EncodeToString(encrypted), nil + encrypted := make([]byte, len(text)) + c.Encrypt(encrypted, []byte(text)) + return hex.EncodeToString(encrypted) } -func Decrypt(key []byte, ct string) (string, error) { +func DecryptString(key []byte, encrypted string) string { c, err := aes.NewCipher(key) if err != nil { - log.Fatalf("encryption key/salt is invalid: %s", err) + log.Fatalf("decryption key/salt is invalid: %s", err) } - decoded, _ := hex.DecodeString(ct) + decoded, _ := hex.DecodeString(encrypted) dest := make([]byte, len(decoded)) c.Decrypt(dest, decoded) s := string(dest[:]) - return s, nil + return s +} + +func HashString(text string) string { + h := sha256.New() + h.Write([]byte(text)) + bs := h.Sum(nil) + return string(bs) } diff --git a/pkg/common/osx/lock.go b/pkg/common/osx/lock.go index 63768e2a..b45c165c 100644 --- a/pkg/common/osx/lock.go +++ b/pkg/common/osx/lock.go @@ -8,16 +8,16 @@ import ( ) type Lock[T comparable] struct { - path string - dataCurrent T + path string + dataProvider func() T } -func NewLock[T comparable](path string, data T) Lock[T] { - return Lock[T]{path: path, dataCurrent: data} +func NewLock[T comparable](path string, dataProvider func() T) Lock[T] { + return Lock[T]{path, dataProvider} } func (l Lock[T]) Lock() error { - err := fmtx.MarshalToFile(l.path, l.dataCurrent) + err := fmtx.MarshalToFile(l.path, l.dataProvider()) if err != nil { return fmt.Errorf("cannot save lock file '%s': %w", l.path, err) } @@ -36,7 +36,7 @@ func (l Lock[T]) IsLocked() bool { } func (l Lock[T]) DataCurrent() T { - return l.dataCurrent + return l.dataProvider() } func (l Lock[T]) DataLocked() (T, error) { @@ -58,6 +58,6 @@ func (l Lock[T]) IsUpToDate() (bool, error) { if err != nil { return false, err } - upToDate := cmp.Equal(l.dataCurrent, dataLocked) + upToDate := cmp.Equal(l.DataCurrent(), dataLocked) return upToDate, nil } diff --git a/pkg/instance/constants.go b/pkg/instance/constants.go index a64d1166..36e6dc21 100644 --- a/pkg/instance/constants.go +++ b/pkg/instance/constants.go @@ -18,3 +18,6 @@ func ProcessingModes() []string { //go:embed resource/cbp.exe var CbpExecutable []byte + +//go:embed resource/oak-run-1.46.0.jar +var OakRunJar []byte diff --git a/pkg/instance/resource/cbp.exe b/pkg/instance/resource/cbp.exe index 82e9eb27597b9d76bba260fa564639f72d2de4e2..384b5a6c355007f85ced3aeea6ac1206f63acfc3 100644 GIT binary patch literal 130 zcmWN_$qmCG5CFixtDu3H1}NCUOOhr%2YtEt3z*+53r#G5}Wli0A|O OfNA0WD?(Z0VEF^#>L&^S literal 15360 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 Date: Wed, 25 Jan 2023 01:19:53 +0100 Subject: [PATCH 03/11] Custom AEM passwords works / initially --- .run/instance_create.run.xml | 12 ---- go.mod | 2 +- pkg/common/cryptox/cryptox.go | 9 +-- pkg/common/tplx/tplx.go | 13 +++++ pkg/http.go | 7 +-- pkg/instance/constants.go | 5 +- pkg/instance/resource/oak-run-1.42.0.jar | 3 + .../resource/oak-run_set-password.groovy | 32 ++++++++++ pkg/local_instance.go | 27 +++++++-- pkg/local_instance_manager.go | 58 ++++++++++--------- pkg/oak_run.go | 51 +++++++++++++--- pkg/sdk.go | 4 ++ 12 files changed, 161 insertions(+), 62 deletions(-) delete mode 100644 .run/instance_create.run.xml create mode 100644 pkg/instance/resource/oak-run-1.42.0.jar create mode 100644 pkg/instance/resource/oak-run_set-password.groovy diff --git a/.run/instance_create.run.xml b/.run/instance_create.run.xml deleted file mode 100644 index bacb334c..00000000 --- a/.run/instance_create.run.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/go.mod b/go.mod index e446d598..e546b96d 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/samber/lo v1.33.0 github.com/segmentio/textio v1.2.0 + github.com/sethvargo/go-password v0.2.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cast v1.4.1 github.com/spf13/cobra v1.6.1 @@ -45,7 +46,6 @@ require ( github.com/pelletier/go-toml v1.9.4 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sethvargo/go-password v0.2.0 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/pkg/common/cryptox/cryptox.go b/pkg/common/cryptox/cryptox.go index 7b5de3a3..4967a316 100644 --- a/pkg/common/cryptox/cryptox.go +++ b/pkg/common/cryptox/cryptox.go @@ -4,7 +4,9 @@ import ( "crypto/aes" "crypto/sha256" "encoding/hex" + "fmt" log "github.com/sirupsen/logrus" + "io" ) func EncryptString(key []byte, text string) string { @@ -30,8 +32,7 @@ func DecryptString(key []byte, encrypted string) string { } func HashString(text string) string { - h := sha256.New() - h.Write([]byte(text)) - bs := h.Sum(nil) - return string(bs) + hash := sha256.New() + _, _ = io.WriteString(hash, text) + return fmt.Sprintf("%x", hash.Sum(nil)) } diff --git a/pkg/common/tplx/tplx.go b/pkg/common/tplx/tplx.go index baa42c6b..67297a08 100644 --- a/pkg/common/tplx/tplx.go +++ b/pkg/common/tplx/tplx.go @@ -1,6 +1,7 @@ package tplx import ( + "bytes" "reflect" "text/template" ) @@ -33,3 +34,15 @@ var funcMap = template.FuncMap{ func recovery() { recover() } + +func RenderString(tplContent string, data any) (string, error) { + tplParsed, err := New("string-template").Parse(tplContent) + if err != nil { + return "", err + } + var tplOutput bytes.Buffer + if err := tplParsed.Execute(&tplOutput, data); err != nil { + return "", err + } + return tplOutput.String(), nil +} diff --git a/pkg/http.go b/pkg/http.go index b266ba26..10aa2cf3 100644 --- a/pkg/http.go +++ b/pkg/http.go @@ -16,22 +16,21 @@ type HTTP struct { func NewHTTP(instance *Instance, baseURL string) *HTTP { return &HTTP{ - client: newInstanceHTTPClient(instance, baseURL), + client: newInstanceHTTPClient(baseURL), instance: instance, } } -func newInstanceHTTPClient(instance *Instance, baseURL string) *resty.Client { +func newInstanceHTTPClient(baseURL string) *resty.Client { client := resty.New() client.SetBaseURL(baseURL) - client.SetBasicAuth(instance.User(), instance.Password()) client.SetDisableWarn(true) client.SetDoNotParseResponse(true) return client } func (hc *HTTP) Request() *resty.Request { - return hc.client.R() + return hc.client.R().SetBasicAuth(hc.instance.User(), hc.instance.Password()) } func (hc *HTTP) RequestFormData(props map[string]any) *resty.Request { diff --git a/pkg/instance/constants.go b/pkg/instance/constants.go index 36e6dc21..a59e2e1b 100644 --- a/pkg/instance/constants.go +++ b/pkg/instance/constants.go @@ -19,5 +19,8 @@ func ProcessingModes() []string { //go:embed resource/cbp.exe var CbpExecutable []byte -//go:embed resource/oak-run-1.46.0.jar +//go:embed resource/oak-run-1.42.0.jar var OakRunJar []byte + +//go:embed resource/oak-run_set-password.groovy +var OakRunSetPassword string diff --git a/pkg/instance/resource/oak-run-1.42.0.jar b/pkg/instance/resource/oak-run-1.42.0.jar new file mode 100644 index 00000000..e073dfb5 --- /dev/null +++ b/pkg/instance/resource/oak-run-1.42.0.jar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44c6570d3a446e9adf5cce9da0804d252f611403b6f2ec699fec3bbcb81ebcab +size 54620288 diff --git a/pkg/instance/resource/oak-run_set-password.groovy b/pkg/instance/resource/oak-run_set-password.groovy new file mode 100644 index 00000000..c9550f35 --- /dev/null +++ b/pkg/instance/resource/oak-run_set-password.groovy @@ -0,0 +1,32 @@ +import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil +import org.apache.jackrabbit.oak.spi.commit.CommitInfo +import org.apache.jackrabbit.oak.spi.commit.EmptyHook + +class Global { + static userNode = null; +} + +void findUserNode(ub) { + if (ub.hasProperty("rep:principalName")) { + if ("rep:principalName = {{.User}}".equals(ub.getProperty("rep:principalName").toString())) { + Global.userNode = ub; + } + } + ub.childNodeNames.each { it -> + if (Global.userNode == null) { + findUserNode(ub.getChildNode(it)); + } + } +} + +ub = session.store.root.builder(); +findUserNode(ub.getChildNode("home").getChildNode("users")); + +if (Global.userNode) { + println("Found user node: " + Global.userNode.toString()); + Global.userNode.setProperty("rep:password", PasswordUtil.buildPasswordHash("{{.Password}}")); + session.store.merge(ub, EmptyHook.INSTANCE, CommitInfo.EMPTY); + println("Updated user node: " + Global.userNode.toString()); +} else { + println("Could not find user node!"); +} diff --git a/pkg/local_instance.go b/pkg/local_instance.go index 1a9fd89a..c4e21727 100644 --- a/pkg/local_instance.go +++ b/pkg/local_instance.go @@ -41,6 +41,7 @@ const ( LocalInstanceScriptStop = "stop" LocalInstanceScriptStatus = "status" LocalInstanceBackupExtension = "aemb.tar.zst" + LocalInstanceUser = "admin" ) func (li LocalInstance) Instance() *Instance { @@ -234,6 +235,10 @@ func (li LocalInstance) Start() error { if err := cmd.Run(); err != nil { return fmt.Errorf("cannot execute start script for instance '%s': %w", li.instance.ID(), err) } + // TODO await only if custom aem password is set and only on first boot + if err := li.AwaitAuthReady(); err != nil { + return err + } if err := li.startLock().Lock(); err != nil { return err } @@ -241,6 +246,12 @@ func (li LocalInstance) Start() error { return nil } +// IsAuthReady checks AEM to set up custom AEM password on first run +func (li LocalInstance) IsAuthReady() bool { + _, err := li.instance.osgi.BundleManager().List() + return err == nil +} + func (li LocalInstance) StartAndAwait() error { if err := li.Start(); err != nil { return err @@ -252,14 +263,16 @@ func (li LocalInstance) StartAndAwait() error { } func (li LocalInstance) updatePassword() error { - if li.IsInitialized() { + if li.startLock().IsLocked() { lock := li.startLock() data, err := lock.DataLocked() if err != nil { return err } if data.Password != lock.DataCurrent().Password { - li.Opts().OakRun.SetPassword(li.QuickstartDir(), lock.DataCurrent().Password) + if err := li.Opts().OakRun.SetPassword(li.Dir(), LocalInstanceUser, li.instance.password); err != nil { + return err + } } } return nil @@ -310,9 +323,6 @@ func (li LocalInstance) Stop() error { if err := cmd.Run(); err != nil { return fmt.Errorf("cannot execute stop script for instance '%s': %w", li.instance.ID(), err) } - if err := li.startLock().Unlock(); err != nil { - return err - } log.Infof("stopped instance '%s' ", li.instance.ID()) return nil } @@ -413,6 +423,13 @@ func (li LocalInstance) AwaitNotRunning() error { return li.Await("not running", func() bool { return !li.IsRunning() }, time.Minute*10) } +func (li LocalInstance) AwaitAuthReady() error { + return li.Await("auth ready", func() bool { + _, err := li.instance.osgi.bundleManager.List() + return err == nil + }, time.Minute*10) +} + type LocalStatus int const ( diff --git a/pkg/local_instance_manager.go b/pkg/local_instance_manager.go index d54e41ef..f2c77f54 100644 --- a/pkg/local_instance_manager.go +++ b/pkg/local_instance_manager.go @@ -40,21 +40,13 @@ func (im *InstanceManager) NewLocalOpts(manager *InstanceManager) *LocalOpts { result := &LocalOpts{ manager: manager, - UnpackDir: UnpackDir, - BackupDir: BackupDir, - JavaOpts: im.aem.javaOpts, - Quickstart: &Quickstart{ - DistFile: DistFile, - LicenseFile: LicenseFile, - }, - } - result.Sdk = &Sdk{ - localOpts: result, - } - result.OakRun = &OakRun{ - localOpts: result, - Source: OakRunSourceEmbedded, + UnpackDir: UnpackDir, + BackupDir: BackupDir, + JavaOpts: im.aem.javaOpts, + Quickstart: NewQuickstart(), } + result.Sdk = NewSdk(result) + result.OakRun = NewOakRun(result) return result } @@ -102,6 +94,13 @@ func IsSdkFile(path string) bool { return pathx.Ext(path) == "zip" } +func NewQuickstart() *Quickstart { + return &Quickstart{ + DistFile: DistFile, + LicenseFile: LicenseFile, + } +} + type Quickstart struct { DistFile string LicenseFile string @@ -221,14 +220,14 @@ func (im *InstanceManager) Start(instances []Instance) ([]Instance, error) { } } + var awaited []Instance if im.CheckOpts.AwaitStrict { - if err := im.AwaitStarted(started); err != nil { - return started, err - } + awaited = started } else { - if err := im.AwaitStarted(instances); err != nil { - return instances, err - } + awaited = instances + } + if err := im.AwaitStarted(awaited); err != nil { + return nil, err } return started, nil @@ -262,16 +261,19 @@ func (im *InstanceManager) Stop(instances []Instance) ([]Instance, error) { } } + var awaited []Instance if im.CheckOpts.AwaitStrict { - if err := im.AwaitStopped(stopped); err != nil { - return stopped, err - } - im.Clean(stopped) + awaited = stopped } else { - if err := im.AwaitStopped(instances); err != nil { - return instances, err - } - im.Clean(instances) + awaited = instances + } + if err := im.AwaitStopped(awaited); err != nil { + return nil, err + } + + _, err = im.Clean(stopped) + if err != nil { + return nil, err } return stopped, nil diff --git a/pkg/oak_run.go b/pkg/oak_run.go index b7591dd3..84f2bd36 100644 --- a/pkg/oak_run.go +++ b/pkg/oak_run.go @@ -1,24 +1,36 @@ package pkg import ( - _ "embed" "fmt" log "github.com/sirupsen/logrus" "github.com/wttech/aemc/pkg/common/filex" "github.com/wttech/aemc/pkg/common/httpx" "github.com/wttech/aemc/pkg/common/osx" + "github.com/wttech/aemc/pkg/common/pathx" + "github.com/wttech/aemc/pkg/common/tplx" "github.com/wttech/aemc/pkg/instance" + "os/exec" "path/filepath" ) const ( - OakRunSourceEmbedded = "embedded/oak-run-1.46.0.jar" + OakRunSourceEmbedded = "embedded/oak-run-1.42.0.jar" ) +func NewOakRun(localOpts *LocalOpts) *OakRun { + return &OakRun{ + localOpts: localOpts, + + Source: OakRunSourceEmbedded, + StorePath: "crx-quickstart/repository/segmentstore", + } +} + type OakRun struct { localOpts *LocalOpts - Source string + Source string + StorePath string } type OakRunLock struct { @@ -59,14 +71,14 @@ func (or OakRun) Prepare() error { } func (or OakRun) JarFile() string { - return fmt.Sprintf("%s/%s", or.Dir(), filepath.Base(or.Source)) + return pathx.Abs(fmt.Sprintf("%s/%s", or.Dir(), filepath.Base(or.Source))) } func (or OakRun) prepare() error { jarFile := or.JarFile() if or.Source == OakRunSourceEmbedded { log.Infof("copying embedded Oak Run JAR to file '%s'", jarFile) - if err := filex.Write(jarFile, instance.CbpExecutable); err != nil { + if err := filex.Write(jarFile, instance.OakRunJar); err != nil { return fmt.Errorf("cannot embedded Oak Run JAR to file '%s': %s", jarFile, err) } log.Infof("copied embedded Oak Run JAR to file '%s'", jarFile) @@ -80,7 +92,32 @@ func (or OakRun) prepare() error { return nil } -func (or OakRun) SetPassword(quickstartDir string, password string) error { - log.Infof("setting password for instance quickstart dir '%s'", quickstartDir) +func (or OakRun) SetPassword(instanceDir string, user string, password string) error { + log.Infof("password setting for user '%s' on instance at dir '%s'", user, instanceDir) + if err := or.RunScript(instanceDir, "set-password", instance.OakRunSetPassword, map[string]any{"User": user, "Password": password}); err != nil { + return err + } + log.Infof("password set for user '%s' on instance at dir '%s'", user, instanceDir) + return nil +} + +func (or OakRun) RunScript(instanceDir string, scriptName, scriptTpl string, scriptData map[string]any) error { + scriptContent, err := tplx.RenderString(scriptTpl, scriptData) + if err != nil { + return err + } + scriptFile := fmt.Sprintf("%s/aem-compose/oak-run/%s.groovy", instanceDir, scriptName) + if err := filex.WriteString(scriptFile, scriptContent); err != nil { + return fmt.Errorf("cannot save Oak Run script '%s': %w", scriptFile, err) + } + defer func() { + // TODO pathx.DeleteIfExists(scriptFile) + }() + storeDir := fmt.Sprintf("%s/%s", instanceDir, or.StorePath) + cmd := exec.Command("java", "-jar", or.JarFile(), "console", storeDir, "--read-write", fmt.Sprintf(":load %s", scriptFile)) + or.localOpts.manager.aem.CommandOutput(cmd) + if err := cmd.Run(); err != nil { + return fmt.Errorf("cannot run Oak Run script '%s': %w", scriptFile, err) + } return nil } diff --git a/pkg/sdk.go b/pkg/sdk.go index 3a90ccb7..03b5eb39 100644 --- a/pkg/sdk.go +++ b/pkg/sdk.go @@ -10,6 +10,10 @@ import ( "path/filepath" ) +func NewSdk(localOpts *LocalOpts) *Sdk { + return &Sdk{localOpts: localOpts} +} + type Sdk struct { localOpts *LocalOpts } From 341d82fecd9cb25db5a9be79d221779de9c3d28d Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Wed, 25 Jan 2023 01:27:33 +0100 Subject: [PATCH 04/11] Base salt cleaned up --- pkg/cfg/config.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/cfg/config.go b/pkg/cfg/config.go index 8eef15b1..fc542bc9 100644 --- a/pkg/cfg/config.go +++ b/pkg/cfg/config.go @@ -4,7 +4,6 @@ import ( "bytes" _ "embed" "fmt" - "github.com/sethvargo/go-password/password" log "github.com/sirupsen/logrus" "github.com/spf13/viper" "github.com/wttech/aemc/pkg/common" @@ -132,9 +131,7 @@ func renderConfigYml() (string, error) { return "", err } var tplOutput bytes.Buffer - if err := tplParsed.Execute(&tplOutput, map[string]any{ - "BASE_SALT": password.MustGenerate(16, 2, 2, false, false), - }); err != nil { + if err := tplParsed.Execute(&tplOutput, map[string]any{}); err != nil { return "", err } return tplOutput.String(), nil From e6057de40dd8244f417e692d46aeff573acf6558 Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Wed, 25 Jan 2023 01:34:07 +0100 Subject: [PATCH 05/11] Hardening --- pkg/local_instance.go | 12 ++++-------- pkg/oak_run.go | 4 ++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/pkg/local_instance.go b/pkg/local_instance.go index c4e21727..7325bc35 100644 --- a/pkg/local_instance.go +++ b/pkg/local_instance.go @@ -42,6 +42,7 @@ const ( LocalInstanceScriptStatus = "status" LocalInstanceBackupExtension = "aemb.tar.zst" LocalInstanceUser = "admin" + LocalInstanceWorkDirName = "aem-compose" ) func (li LocalInstance) Instance() *Instance { @@ -82,7 +83,7 @@ func (li LocalInstance) Dir() string { } func (li LocalInstance) WorkDir() string { - return fmt.Sprintf("%s/%s", li.Dir(), "aem-compose") + return fmt.Sprintf("%s/%s", li.Dir(), LocalInstanceWorkDirName) } func (li LocalInstance) LockDir() string { @@ -200,7 +201,7 @@ func (li LocalInstance) correctFiles() error { ) content = strings.ReplaceAll(content, // force instance to be launched in background (it is windowed by default) "start \"CQ\" cmd.exe /C java %CQ_JVM_OPTS% -jar %CurrDirName%\\%CQ_JARFILE% %START_OPTS%", - "aem-compose\\bin\\cbp.exe cmd.exe /C \"java %CQ_JVM_OPTS% -jar %CurrDirName%\\%CQ_JARFILE% %START_OPTS% 1> %CurrDirName%\\logs\\stdout.log 2>&1\"", + LocalInstanceWorkDirName+"\\bin\\cbp.exe cmd.exe /C \"java %CQ_JVM_OPTS% -jar %CurrDirName%\\%CQ_JARFILE% %START_OPTS% 1> %CurrDirName%\\logs\\stdout.log 2>&1\"", ) content = strings.ReplaceAll(content, // introduce CQ_START_OPTS (not available by default) "set START_OPTS=start -c %CurrDirName% -i launchpad", @@ -246,12 +247,6 @@ func (li LocalInstance) Start() error { return nil } -// IsAuthReady checks AEM to set up custom AEM password on first run -func (li LocalInstance) IsAuthReady() bool { - _, err := li.instance.osgi.BundleManager().List() - return err == nil -} - func (li LocalInstance) StartAndAwait() error { if err := li.Start(); err != nil { return err @@ -423,6 +418,7 @@ func (li LocalInstance) AwaitNotRunning() error { return li.Await("not running", func() bool { return !li.IsRunning() }, time.Minute*10) } +// TODO make it more verbose about actual bundle progress func (li LocalInstance) AwaitAuthReady() error { return li.Await("auth ready", func() bool { _, err := li.instance.osgi.bundleManager.List() diff --git a/pkg/oak_run.go b/pkg/oak_run.go index 84f2bd36..30e94e85 100644 --- a/pkg/oak_run.go +++ b/pkg/oak_run.go @@ -106,12 +106,12 @@ func (or OakRun) RunScript(instanceDir string, scriptName, scriptTpl string, scr if err != nil { return err } - scriptFile := fmt.Sprintf("%s/aem-compose/oak-run/%s.groovy", instanceDir, scriptName) + scriptFile := fmt.Sprintf("%s/%s/tmp/oak-run/%s.groovy", instanceDir, LocalInstanceWorkDirName, scriptName) if err := filex.WriteString(scriptFile, scriptContent); err != nil { return fmt.Errorf("cannot save Oak Run script '%s': %w", scriptFile, err) } defer func() { - // TODO pathx.DeleteIfExists(scriptFile) + pathx.DeleteIfExists(scriptFile) }() storeDir := fmt.Sprintf("%s/%s", instanceDir, or.StorePath) cmd := exec.Command("java", "-jar", or.JarFile(), "console", storeDir, "--read-write", fmt.Sprintf(":load %s", scriptFile)) From b33ce1da0df9e49bfc9a9507c94f2c98c2f2246a Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Wed, 25 Jan 2023 08:47:55 +0100 Subject: [PATCH 06/11] Clean up --- pkg/instance/resource/oak-run-1.46.0.jar | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 pkg/instance/resource/oak-run-1.46.0.jar diff --git a/pkg/instance/resource/oak-run-1.46.0.jar b/pkg/instance/resource/oak-run-1.46.0.jar deleted file mode 100644 index f26b388e..00000000 --- a/pkg/instance/resource/oak-run-1.46.0.jar +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0592b30d9500aededbaff2d51cf0ce6a92f601d8f0f59d2a2e5193d8f9906b4b -size 545392 From eda768e77d9107bcdd486b54dd3a88e01f761dd3 Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Wed, 25 Jan 2023 15:59:50 +0100 Subject: [PATCH 07/11] Up-to-date problem fixed --- go.mod | 1 - go.sum | 2 -- pkg/cfg/values.go | 1 + pkg/instance.go | 55 ++++++++++++----------------------- pkg/instance/constants.go | 20 +++++++++++++ pkg/instance_manager.go | 6 ++-- pkg/local_instance.go | 40 +++++++++++++------------ pkg/local_instance_manager.go | 6 ++++ pkg/oak_run.go | 4 ++- pkg/sdk.go | 6 +++- pkg/status.go | 7 +++-- 11 files changed, 82 insertions(+), 66 deletions(-) diff --git a/go.mod b/go.mod index e546b96d..98dc33ab 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/samber/lo v1.33.0 github.com/segmentio/textio v1.2.0 - github.com/sethvargo/go-password v0.2.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cast v1.4.1 github.com/spf13/cobra v1.6.1 diff --git a/go.sum b/go.sum index 4a2c543b..e96b786b 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,6 @@ github.com/samber/lo v1.33.0 h1:2aKucr+rQV6gHpY3bpeZu69uYoQOzVhGT3J22Op6Cjk= github.com/samber/lo v1.33.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= github.com/segmentio/textio v1.2.0 h1:Ug4IkV3kh72juJbG8azoSBlgebIbUUxVNrfFcKHfTSQ= github.com/segmentio/textio v1.2.0/go.mod h1:+Rb7v0YVODP+tK5F7FD9TCkV7gOYx9IgLHWiqtvY8ag= -github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= -github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= diff --git a/pkg/cfg/values.go b/pkg/cfg/values.go index 0038cf88..c6753ec8 100644 --- a/pkg/cfg/values.go +++ b/pkg/cfg/values.go @@ -88,6 +88,7 @@ type ConfigValues struct { Local struct { UnpackDir string `mapstructure:"unpack_dir" yaml:"unpack_dir"` + ToolDir string `mapstructure:"tool_dir" yaml:"tool_dir"` BackupDir string `mapstructure:"backup_dir" yaml:"backup_dir"` Quickstart struct { diff --git a/pkg/instance.go b/pkg/instance.go index 06b70698..75ff9586 100644 --- a/pkg/instance.go +++ b/pkg/instance.go @@ -7,32 +7,13 @@ import ( "github.com/samber/lo" log "github.com/sirupsen/logrus" "github.com/wttech/aemc/pkg/common/fmtx" + "github.com/wttech/aemc/pkg/instance" "golang.org/x/exp/maps" nurl "net/url" "strings" "time" ) -const ( - IDDelimiter = "_" - URLLocalAuthor = "http://localhost:4502" - URLLocalPublish = "http://localhost:4503" - PasswordDefault = "admin" - UserDefault = "admin" - LocationLocal = "local" - LocationRemote = "remote" - RoleAuthorPortSuffix = "02" - ClassifierDefault = "" - AemVersionUnknown = "unknown" -) - -type Role string - -const ( - RoleAuthor Role = "author" - RolePublish Role = "publish" -) - // Instance represents AEM instance type Instance struct { manager *InstanceManager @@ -109,28 +90,28 @@ func (i Instance) PackageManager() *PackageManager { } func (i Instance) IDInfo() IDInfo { - parts := strings.Split(i.id, IDDelimiter) + parts := strings.Split(i.id, instance.IDDelimiter) if len(parts) == 2 { return IDInfo{ Location: parts[0], - Role: Role(parts[1]), + Role: instance.Role(parts[1]), } } return IDInfo{ Location: parts[0], - Role: Role(parts[1]), + Role: instance.Role(parts[1]), Classifier: parts[2], } } type IDInfo struct { Location string - Role Role + Role instance.Role Classifier string } func (i Instance) IsLocal() bool { - return i.IDInfo().Location == LocationLocal + return i.IDInfo().Location == instance.LocationLocal } func (i Instance) IsRemote() bool { @@ -138,35 +119,35 @@ func (i Instance) IsRemote() bool { } func (i Instance) IsAuthor() bool { - return i.IDInfo().Role == RoleAuthor + return i.IDInfo().Role == instance.RoleAuthor } func (i Instance) IsPublish() bool { - return i.IDInfo().Role == RolePublish + return i.IDInfo().Role == instance.RolePublish } func locationByURL(config *nurl.URL) string { if lo.Contains(localHosts(), config.Hostname()) { - return LocationLocal + return instance.LocationLocal } - return LocationRemote + return instance.LocationRemote } -func roleByURL(config *nurl.URL) Role { - if strings.HasSuffix(config.Port(), RoleAuthorPortSuffix) { - return RoleAuthor +func roleByURL(config *nurl.URL) instance.Role { + if strings.HasSuffix(config.Port(), instance.RoleAuthorPortSuffix) { + return instance.RoleAuthor } - return RolePublish + return instance.RolePublish } // TODO local-publish-preview etc func classifierByURL(_ *nurl.URL) string { - return ClassifierDefault + return instance.ClassifierDefault } func credentialsByURL(config *nurl.URL) (string, string) { - user := UserDefault - pwd := PasswordDefault + user := instance.UserDefault + pwd := instance.PasswordDefault urlUser := config.User.Username() if urlUser != "" { @@ -199,7 +180,7 @@ func (i Instance) AemVersion() string { version, err := i.status.AemVersion() if err != nil { log.Debugf("cannot determine AEM version of instance '%s': %s", i.id, err) - return AemVersionUnknown + return instance.AemVersionUnknown } return version } diff --git a/pkg/instance/constants.go b/pkg/instance/constants.go index a59e2e1b..c0f7e0f0 100644 --- a/pkg/instance/constants.go +++ b/pkg/instance/constants.go @@ -4,6 +4,19 @@ import ( _ "embed" ) +const ( + IDDelimiter = "_" + URLLocalAuthor = "http://localhost:4502" + URLLocalPublish = "http://localhost:4503" + PasswordDefault = "admin" + UserDefault = "admin" + LocationLocal = "local" + LocationRemote = "remote" + RoleAuthorPortSuffix = "02" + ClassifierDefault = "" + AemVersionUnknown = "unknown" +) + type ProcessingMode string const ( @@ -24,3 +37,10 @@ var OakRunJar []byte //go:embed resource/oak-run_set-password.groovy var OakRunSetPassword string + +type Role string + +const ( + RoleAuthor Role = "author" + RolePublish Role = "publish" +) diff --git a/pkg/instance_manager.go b/pkg/instance_manager.go index 5dc9c42b..13be92d4 100644 --- a/pkg/instance_manager.go +++ b/pkg/instance_manager.go @@ -188,12 +188,12 @@ func (im *InstanceManager) CheckOnce(instances []Instance, checks []Checker) (bo } func (im *InstanceManager) NewLocalAuthor() Instance { - i, _ := im.NewByURL(URLLocalAuthor) + i, _ := im.NewByURL(instance.URLLocalAuthor) return *i } func (im *InstanceManager) NewLocalPublish() Instance { - i, _ := im.NewByURL(URLLocalPublish) + i, _ := im.NewByURL(instance.URLLocalPublish) return *i } @@ -216,7 +216,7 @@ func (im *InstanceManager) NewByURL(url string) (*Instance, error) { if len(classifier) > 0 { parts = append(parts, classifier) } - id := strings.Join(parts, IDDelimiter) + id := strings.Join(parts, instance.IDDelimiter) return im.New(id, url, user, password), nil } diff --git a/pkg/local_instance.go b/pkg/local_instance.go index 7325bc35..b268a60e 100644 --- a/pkg/local_instance.go +++ b/pkg/local_instance.go @@ -73,7 +73,7 @@ func (li LocalInstance) Opts() *LocalOpts { func (li LocalInstance) Name() string { id := li.instance.IDInfo() if id.Classifier != "" { - return string(id.Role) + IDDelimiter + id.Classifier + return string(id.Role) + instance.IDDelimiter + id.Classifier } return string(id.Role) } @@ -224,7 +224,7 @@ func (li LocalInstance) Start() error { if !li.IsCreated() { return fmt.Errorf("cannot start instance '%s' as it is not created", li.instance.ID()) } - if err := li.updatePassword(); err != nil { + if err := li.updateAuth(); err != nil { return err } log.Infof("starting instance '%s' ", li.instance.ID()) @@ -236,8 +236,7 @@ func (li LocalInstance) Start() error { if err := cmd.Run(); err != nil { return fmt.Errorf("cannot execute start script for instance '%s': %w", li.instance.ID(), err) } - // TODO await only if custom aem password is set and only on first boot - if err := li.AwaitAuthReady(); err != nil { + if err := li.awaitAuth(); err != nil { return err } if err := li.startLock().Lock(); err != nil { @@ -257,18 +256,19 @@ func (li LocalInstance) StartAndAwait() error { return nil } -func (li LocalInstance) updatePassword() error { - if li.startLock().IsLocked() { - lock := li.startLock() - data, err := lock.DataLocked() - if err != nil { +func (li LocalInstance) updateAuth() error { + if !li.IsInitialized() { + return nil + } + lock := li.startLock() + data, err := lock.DataLocked() + if err != nil { + return err + } + if data.Password != lock.DataCurrent().Password { + if err := li.Opts().OakRun.SetPassword(li.Dir(), LocalInstanceUser, li.instance.password); err != nil { return err } - if data.Password != lock.DataCurrent().Password { - if err := li.Opts().OakRun.SetPassword(li.Dir(), LocalInstanceUser, li.instance.password); err != nil { - return err - } - } } return nil } @@ -293,8 +293,8 @@ func (li LocalInstance) startLock() osx.Lock[localInstanceStartLock] { return localInstanceStartLock{ Version: li.Version, HTTPPort: li.instance.HTTP().Port(), - RunModes: li.RunModesString(), - JVMOpts: li.JVMOptsString(), + RunModes: strings.Join(li.RunModes, ","), + JVMOpts: strings.Join(li.JvmOpts, " "), Password: cryptox.HashString(li.instance.password), } }) @@ -418,8 +418,12 @@ func (li LocalInstance) AwaitNotRunning() error { return li.Await("not running", func() bool { return !li.IsRunning() }, time.Minute*10) } -// TODO make it more verbose about actual bundle progress -func (li LocalInstance) AwaitAuthReady() error { +// awaitAuth waits for a custom password to be in use (initially the default one is used instead) +func (li LocalInstance) awaitAuth() error { + if li.IsInitialized() || li.instance.password == instance.PasswordDefault { + return nil + } + // TODO 'local_author | auth not ready (1/588=2%): xtg' return li.Await("auth ready", func() bool { _, err := li.instance.osgi.bundleManager.List() return err == nil diff --git a/pkg/local_instance_manager.go b/pkg/local_instance_manager.go index f2c77f54..df4388fd 100644 --- a/pkg/local_instance_manager.go +++ b/pkg/local_instance_manager.go @@ -19,6 +19,7 @@ import ( const ( UnpackDir = "aem/home/data/instance" BackupDir = "aem/home/data/backup" + ToolDir = "aem/home/opt" LibDir = "aem/home/lib" DistFile = LibDir + "/aem-sdk-quickstart.jar" LicenseFile = LibDir + "/" + LicenseFilename @@ -29,6 +30,7 @@ type LocalOpts struct { manager *InstanceManager UnpackDir string + ToolDir string BackupDir string JavaOpts *java.Opts OakRun *OakRun @@ -41,6 +43,7 @@ func (im *InstanceManager) NewLocalOpts(manager *InstanceManager) *LocalOpts { manager: manager, UnpackDir: UnpackDir, + ToolDir: ToolDir, BackupDir: BackupDir, JavaOpts: im.aem.javaOpts, Quickstart: NewQuickstart(), @@ -457,6 +460,9 @@ func (im *InstanceManager) configureLocalOpts(config *cfg.Config) { if len(opts.UnpackDir) > 0 { im.LocalOpts.UnpackDir = opts.UnpackDir } + if len(opts.ToolDir) > 0 { + im.LocalOpts.ToolDir = opts.ToolDir + } if len(opts.BackupDir) > 0 { im.LocalOpts.BackupDir = opts.BackupDir } diff --git a/pkg/oak_run.go b/pkg/oak_run.go index 30e94e85..802b613f 100644 --- a/pkg/oak_run.go +++ b/pkg/oak_run.go @@ -15,6 +15,7 @@ import ( const ( OakRunSourceEmbedded = "embedded/oak-run-1.42.0.jar" + OakRunToolDirName = "oak-run" ) func NewOakRun(localOpts *LocalOpts) *OakRun { @@ -38,7 +39,7 @@ type OakRunLock struct { } func (or OakRun) Dir() string { - return or.localOpts.UnpackDir + "/oak-run" + return or.localOpts.ToolDir + "/" + OakRunToolDirName } func (or OakRun) lock() osx.Lock[OakRunLock] { @@ -114,6 +115,7 @@ func (or OakRun) RunScript(instanceDir string, scriptName, scriptTpl string, scr pathx.DeleteIfExists(scriptFile) }() storeDir := fmt.Sprintf("%s/%s", instanceDir, or.StorePath) + // TODO https://issues.apache.org/jira/browse/OAK-5961 (handle JAnsi problem) cmd := exec.Command("java", "-jar", or.JarFile(), "console", storeDir, "--read-write", fmt.Sprintf(":load %s", scriptFile)) or.localOpts.manager.aem.CommandOutput(cmd) if err := cmd.Run(); err != nil { diff --git a/pkg/sdk.go b/pkg/sdk.go index 03b5eb39..eae741ec 100644 --- a/pkg/sdk.go +++ b/pkg/sdk.go @@ -10,6 +10,10 @@ import ( "path/filepath" ) +const ( + SdkToolDirName = "sdk" +) + func NewSdk(localOpts *LocalOpts) *Sdk { return &Sdk{localOpts: localOpts} } @@ -19,7 +23,7 @@ type Sdk struct { } func (s Sdk) Dir() string { - return s.localOpts.UnpackDir + "/sdk" + return s.localOpts.ToolDir + "/" + SdkToolDirName } type SdkLock struct { diff --git a/pkg/status.go b/pkg/status.go index c45989f0..0dedf2bc 100644 --- a/pkg/status.go +++ b/pkg/status.go @@ -5,6 +5,7 @@ import ( "github.com/samber/lo" log "github.com/sirupsen/logrus" "github.com/wttech/aemc/pkg/common/fmtx" + "github.com/wttech/aemc/pkg/instance" "io" "regexp" "strings" @@ -67,11 +68,11 @@ func (sm Status) TimeLocation() (*time.Location, error) { func (sm Status) AemVersion() (string, error) { response, err := sm.instance.http.Request().Get(SystemProductInfoPath) if err != nil { - return AemVersionUnknown, fmt.Errorf("cannot read system product info on instance '%s'", sm.instance.id) + return instance.AemVersionUnknown, fmt.Errorf("cannot read system product info on instance '%s'", sm.instance.id) } bytes, err := io.ReadAll(response.RawBody()) if err != nil { - return AemVersionUnknown, fmt.Errorf("cannot read system product info on instance '%s': %w", sm.instance.id, err) + return instance.AemVersionUnknown, fmt.Errorf("cannot read system product info on instance '%s': %w", sm.instance.id, err) } lines := string(bytes) for _, line := range strings.Split(lines, "\n") { @@ -80,5 +81,5 @@ func (sm Status) AemVersion() (string, error) { return matches[1], nil } } - return AemVersionUnknown, nil + return instance.AemVersionUnknown, nil } From 0e9e85e01797faa9b16155ed482cfcb0613c0bbd Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Thu, 26 Jan 2023 10:44:06 +0100 Subject: [PATCH 08/11] Major FS layout changes --- cmd/aem/cli.go | 2 +- cmd/aem/config.go | 5 +- cmd/aem/init.go | 26 ++++++ cmd/aem/root.go | 1 + pkg/cfg/config.go | 85 ++++++++++--------- pkg/cfg/values.go | 4 + pkg/common/constants.go | 19 ++++- pkg/common/tplx/tplx.go | 13 +++ pkg/instance/constants.go | 5 +- pkg/instance/resource/oak-run-1.42.0.jar | 3 - .../set-password.groovy} | 0 pkg/local_instance.go | 12 +-- pkg/local_instance_manager.go | 20 +++-- pkg/oak_run.go | 69 +++++++-------- pkg/package_manager.go | 4 +- pkg/sdk.go | 2 +- {pkg/cfg => project/aem/default/etc}/aem.yml | 7 +- 17 files changed, 168 insertions(+), 109 deletions(-) create mode 100644 cmd/aem/init.go delete mode 100644 pkg/instance/resource/oak-run-1.42.0.jar rename pkg/instance/resource/{oak-run_set-password.groovy => oak-run/set-password.groovy} (100%) rename {pkg/cfg => project/aem/default/etc}/aem.yml (91%) mode change 100644 => 100755 diff --git a/cmd/aem/cli.go b/cmd/aem/cli.go index 487c763c..d643a449 100644 --- a/cmd/aem/cli.go +++ b/cmd/aem/cli.go @@ -25,7 +25,7 @@ import ( ) const ( - OutputFileDefault = common.HomeDir + "/aem.log" + OutputFileDefault = common.LogDir + "/aem.log" ) type CLI struct { diff --git a/cmd/aem/config.go b/cmd/aem/config.go index a72bbf8d..912daa2e 100644 --- a/cmd/aem/config.go +++ b/cmd/aem/config.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "github.com/spf13/cobra" "github.com/wttech/aemc/pkg/cfg" ) @@ -35,9 +34,9 @@ func (c *CLI) configInitCmd() *cobra.Command { Use: "init", Short: "Initialize configuration", Run: func(cmd *cobra.Command, args []string) { - err := c.config.Init() + err := c.config.Initialize() if err != nil { - c.Fail(fmt.Sprintf("cannot initialize config: %s", err)) + c.Error(err) return } c.SetOutput("path", cfg.File()) diff --git a/cmd/aem/init.go b/cmd/aem/init.go new file mode 100644 index 00000000..a69b360a --- /dev/null +++ b/cmd/aem/init.go @@ -0,0 +1,26 @@ +package main + +import "github.com/spf13/cobra" + +func (c *CLI) initCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "initialize", + Aliases: []string{"init"}, + Short: "Initializes configuration and dependencies", + Run: func(cmd *cobra.Command, args []string) { + if !c.config.IsInitialized() { + if err := c.config.Initialize(); err != nil { + c.Error(err) + return + } + } + if err := c.aem.InstanceManager().LocalOpts.OakRun.Prepare(); err != nil { + c.Error(err) + return + } + c.Ok("initialized properly") + + }, + } + return cmd +} diff --git a/cmd/aem/root.go b/cmd/aem/root.go index 0e034ce2..14bf3a40 100644 --- a/cmd/aem/root.go +++ b/cmd/aem/root.go @@ -22,6 +22,7 @@ func (c *CLI) rootCmd() *cobra.Command { } cmd.AddCommand(c.appCmd()) cmd.AddCommand(c.versionCmd()) + cmd.AddCommand(c.initCmd()) cmd.AddCommand(c.configCmd()) cmd.AddCommand(c.instanceCmd()) cmd.AddCommand(c.osgiCmd()) diff --git a/pkg/cfg/config.go b/pkg/cfg/config.go index fc542bc9..b0a2edc3 100644 --- a/pkg/cfg/config.go +++ b/pkg/cfg/config.go @@ -2,7 +2,6 @@ package cfg import ( "bytes" - _ "embed" "fmt" log "github.com/sirupsen/logrus" "github.com/spf13/viper" @@ -18,11 +17,13 @@ import ( ) const ( - EnvPrefix = "AEM" - InputStdin = "STDIN" - OutputFileDefault = common.HomeDir + "/aem.log" - FilePathDefault = common.HomeDir + "/aem.yml" - FilePathEnvVar = "AEM_CONFIG_FILE" + EnvPrefix = "AEM" + InputStdin = "STDIN" + OutputFileDefault = common.LogDir + "/aem.log" + FileDefault = common.ConfigDir + "/aem.yml" + FileEnvVar = "AEM_CONFIG_FILE" + TemplateFileDefault = common.DefaultDir + "/" + common.ConfigDirName + "/aem.yml" + TemplateFileEnvVar = "AEM_CONFIG_TEMPLATE" ) // Config defines a place for managing input configuration from various sources (YML file, env vars, etc) @@ -102,53 +103,48 @@ func readFromFile(v *viper.Viper) { } func File() string { - path := os.Getenv(FilePathEnvVar) - if len(path) == 0 { - path = FilePathDefault + path := os.Getenv(FileEnvVar) + if path == "" { + path = FileDefault } return path } -func (c *Config) ConfigureLogger() { - log.SetFormatter(&log.TextFormatter{ - TimestampFormat: c.values.Log.TimestampFormat, - FullTimestamp: c.values.Log.FullTimestamp, - }) - - level, err := log.ParseLevel(c.values.Log.Level) - if err != nil { - log.Fatalf("unsupported log level specified: '%s'", c.values.Log.Level) +func TemplateFile() string { + path := os.Getenv(TemplateFileEnvVar) + if path == "" { + path = TemplateFileDefault } - log.SetLevel(level) + return path } -//go:embed aem.yml -var configYml string - -func renderConfigYml() (string, error) { - tplParsed, err := tplx.New("config-tpl").Delims("[[", "]]").Parse(configYml) - if err != nil { - return "", err - } - var tplOutput bytes.Buffer - if err := tplParsed.Execute(&tplOutput, map[string]any{}); err != nil { - return "", err - } - return tplOutput.String(), nil +func (c *Config) IsInitialized() bool { + return pathx.Exists(File()) } -func (c *Config) Init() error { +func (c *Config) Initialize() error { file := File() if pathx.Exists(file) { return fmt.Errorf("config file already exists: '%s'", file) } - - configYmlRendered, err := renderConfigYml() + templateFile := TemplateFile() + if !pathx.Exists(templateFile) { + return fmt.Errorf("config file template does not exist: '%s'", templateFile) + } + ymlTplStr, err := filex.ReadString(templateFile) + if err != nil { + return err + } + ymlTplParsed, err := tplx.New("config-tpl").Delims("[[", "]]").Parse(ymlTplStr) if err != nil { - return fmt.Errorf("cannot render initial config file from template: '%w'", err) + return fmt.Errorf("cannot parse config file template '%s': '%w'", templateFile, err) + } + var yml bytes.Buffer + if err := ymlTplParsed.Execute(&yml, map[string]any{ /* TODO future hook (not sure if needed or not */ }); err != nil { + return fmt.Errorf("cannot render config file template '%s': '%w'", templateFile, err) } - if err = filex.WriteString(file, configYmlRendered); err != nil { - return fmt.Errorf("cannot create initial config file '%s': '%w'", file, err) + if err = filex.WriteString(file, yml.String()); err != nil { + return fmt.Errorf("cannot save config file '%s': '%w'", file, err) } return nil } @@ -157,7 +153,18 @@ func InputFormats() []string { return []string{fmtx.YML, fmtx.JSON} } -// OutputFormats returns all available output formats func OutputFormats() []string { return []string{fmtx.Text, fmtx.YML, fmtx.JSON, fmtx.None} } + +func (c *Config) ConfigureLogger() { + log.SetFormatter(&log.TextFormatter{ + TimestampFormat: c.values.Log.TimestampFormat, + FullTimestamp: c.values.Log.FullTimestamp, + }) + level, err := log.ParseLevel(c.values.Log.Level) + if err != nil { + log.Fatalf("unsupported log level specified: '%s'", c.values.Log.Level) + } + log.SetLevel(level) +} diff --git a/pkg/cfg/values.go b/pkg/cfg/values.go index c6753ec8..52471347 100644 --- a/pkg/cfg/values.go +++ b/pkg/cfg/values.go @@ -95,6 +95,10 @@ type ConfigValues struct { DistFile string `mapstructure:"dist_file" yaml:"dist_file"` LicenseFile string `mapstructure:"license_file" yaml:"license_file"` } `mapstructure:"quickstart" yaml:"quickstart"` + OakRun struct { + DownloadURL string `mapstructure:"download_url" yaml:"download_url"` + StorePath string `mapstructure:"store_path" yaml:"store_path"` + } `mapstructure:"oak_run" yaml:"oak_run"` } `mapstructure:"local" yaml:"local"` Package struct { diff --git a/pkg/common/constants.go b/pkg/common/constants.go index 40786e5d..b482b06e 100644 --- a/pkg/common/constants.go +++ b/pkg/common/constants.go @@ -1,6 +1,21 @@ package common const ( - MainDir = "aem" - HomeDir = MainDir + "/home" + MainDir = "aem" + HomeDirName = "home" + HomeDir = MainDir + "/" + HomeDirName + VarDirName = "var" + VarDir = HomeDir + "/" + VarDirName + ConfigDirName = "etc" + ConfigDir = HomeDir + "/" + ConfigDirName + LogDirName = "log" + LogDir = VarDir + "/" + LogDirName + ToolDirName = "opt" + ToolDir = HomeDir + "/" + ToolDirName + LibDirName = "lib" + LibDir = HomeDir + "/" + LibDirName + TmpDirName = "tmp" + TmpDir = HomeDir + "/" + TmpDirName + DefaultDirName = "default" + DefaultDir = MainDir + "/" + DefaultDirName ) diff --git a/pkg/common/tplx/tplx.go b/pkg/common/tplx/tplx.go index 67297a08..1f075758 100644 --- a/pkg/common/tplx/tplx.go +++ b/pkg/common/tplx/tplx.go @@ -2,6 +2,8 @@ package tplx import ( "bytes" + "fmt" + "github.com/wttech/aemc/pkg/common/filex" "reflect" "text/template" ) @@ -46,3 +48,14 @@ func RenderString(tplContent string, data any) (string, error) { } return tplOutput.String(), nil } + +func RenderFile(file string, content string, data map[string]any) error { + scriptContent, err := RenderString(content, data) + if err != nil { + return err + } + if err := filex.WriteString(file, scriptContent); err != nil { + return fmt.Errorf("cannot render template file '%s': %w", file, err) + } + return nil +} diff --git a/pkg/instance/constants.go b/pkg/instance/constants.go index c0f7e0f0..8bccd743 100644 --- a/pkg/instance/constants.go +++ b/pkg/instance/constants.go @@ -32,10 +32,7 @@ func ProcessingModes() []string { //go:embed resource/cbp.exe var CbpExecutable []byte -//go:embed resource/oak-run-1.42.0.jar -var OakRunJar []byte - -//go:embed resource/oak-run_set-password.groovy +//go:embed resource/oak-run/set-password.groovy var OakRunSetPassword string type Role string diff --git a/pkg/instance/resource/oak-run-1.42.0.jar b/pkg/instance/resource/oak-run-1.42.0.jar deleted file mode 100644 index e073dfb5..00000000 --- a/pkg/instance/resource/oak-run-1.42.0.jar +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:44c6570d3a446e9adf5cce9da0804d252f611403b6f2ec699fec3bbcb81ebcab -size 54620288 diff --git a/pkg/instance/resource/oak-run_set-password.groovy b/pkg/instance/resource/oak-run/set-password.groovy similarity index 100% rename from pkg/instance/resource/oak-run_set-password.groovy rename to pkg/instance/resource/oak-run/set-password.groovy diff --git a/pkg/local_instance.go b/pkg/local_instance.go index b268a60e..79f1547d 100644 --- a/pkg/local_instance.go +++ b/pkg/local_instance.go @@ -148,7 +148,7 @@ func (li LocalInstance) createLock() osx.Lock[localInstanceCreateLock] { } type localInstanceCreateLock struct { - Created time.Time + Created time.Time `yaml:"created"` } func (li LocalInstance) unpackJarFile() error { @@ -301,11 +301,11 @@ func (li LocalInstance) startLock() osx.Lock[localInstanceStartLock] { } type localInstanceStartLock struct { - Version string - JVMOpts string - RunModes string - HTTPPort string - Password string + Version string `yaml:"version"` + JVMOpts string `yaml:"jvm_opts"` + RunModes string `yaml:"run_modes"` + HTTPPort string `yaml:"http_port"` + Password string `yaml:"password"` } func (li LocalInstance) Stop() error { diff --git a/pkg/local_instance_manager.go b/pkg/local_instance_manager.go index df4388fd..302610dd 100644 --- a/pkg/local_instance_manager.go +++ b/pkg/local_instance_manager.go @@ -7,6 +7,7 @@ import ( "github.com/samber/lo" log "github.com/sirupsen/logrus" "github.com/wttech/aemc/pkg/cfg" + "github.com/wttech/aemc/pkg/common" "github.com/wttech/aemc/pkg/common/fmtx" "github.com/wttech/aemc/pkg/common/pathx" "github.com/wttech/aemc/pkg/common/timex" @@ -17,12 +18,11 @@ import ( ) const ( - UnpackDir = "aem/home/data/instance" - BackupDir = "aem/home/data/backup" - ToolDir = "aem/home/opt" - LibDir = "aem/home/lib" - DistFile = LibDir + "/aem-sdk-quickstart.jar" - LicenseFile = LibDir + "/" + LicenseFilename + UnpackDir = common.VarDir + "/instance" + BackupDir = common.VarDir + "/backup" + + DistFile = common.LibDir + "/aem-sdk-quickstart.jar" + LicenseFile = common.LibDir + "/" + LicenseFilename LicenseFilename = "license.properties" ) @@ -43,8 +43,8 @@ func (im *InstanceManager) NewLocalOpts(manager *InstanceManager) *LocalOpts { manager: manager, UnpackDir: UnpackDir, - ToolDir: ToolDir, BackupDir: BackupDir, + ToolDir: common.ToolDir, JavaOpts: im.aem.javaOpts, Quickstart: NewQuickstart(), } @@ -472,4 +472,10 @@ func (im *InstanceManager) configureLocalOpts(config *cfg.Config) { if len(opts.Quickstart.LicenseFile) > 0 { im.LocalOpts.Quickstart.LicenseFile = opts.Quickstart.LicenseFile } + if len(opts.OakRun.DownloadURL) > 0 { + im.LocalOpts.OakRun.DownloadURL = opts.OakRun.DownloadURL + } + if len(opts.OakRun.StorePath) > 0 { + im.LocalOpts.OakRun.StorePath = opts.OakRun.StorePath + } } diff --git a/pkg/oak_run.go b/pkg/oak_run.go index 802b613f..10006589 100644 --- a/pkg/oak_run.go +++ b/pkg/oak_run.go @@ -3,7 +3,6 @@ package pkg import ( "fmt" log "github.com/sirupsen/logrus" - "github.com/wttech/aemc/pkg/common/filex" "github.com/wttech/aemc/pkg/common/httpx" "github.com/wttech/aemc/pkg/common/osx" "github.com/wttech/aemc/pkg/common/pathx" @@ -14,28 +13,27 @@ import ( ) const ( - OakRunSourceEmbedded = "embedded/oak-run-1.42.0.jar" - OakRunToolDirName = "oak-run" + OakRunToolDirName = "oak-run" ) func NewOakRun(localOpts *LocalOpts) *OakRun { return &OakRun{ localOpts: localOpts, - Source: OakRunSourceEmbedded, - StorePath: "crx-quickstart/repository/segmentstore", + DownloadURL: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.44.0/oak-run-1.44.0.jar", + StorePath: "crx-quickstart/repository/segmentstore", } } type OakRun struct { localOpts *LocalOpts - Source string - StorePath string + DownloadURL string + StorePath string } type OakRunLock struct { - Source string + DownloadURL string `yaml:"download_url"` } func (or OakRun) Dir() string { @@ -43,7 +41,7 @@ func (or OakRun) Dir() string { } func (or OakRun) lock() osx.Lock[OakRunLock] { - return osx.NewLock(or.Dir()+"/lock/create.yml", func() OakRunLock { return OakRunLock{Source: or.Source} }) + return osx.NewLock(or.Dir()+"/lock/create.yml", func() OakRunLock { return OakRunLock{DownloadURL: or.DownloadURL} }) } func (or OakRun) Prepare() error { @@ -54,10 +52,10 @@ func (or OakRun) Prepare() error { return err } if upToDate { - log.Debugf("existing instance Oak Run '%s' is up-to-date", lock.DataCurrent().Source) + log.Debugf("existing instance Oak Run '%s' is up-to-date", lock.DataCurrent().DownloadURL) return nil } - log.Infof("preparing new instance Oak Run '%s'", lock.DataCurrent().Source) + log.Infof("preparing new instance Oak Run '%s'", lock.DataCurrent().DownloadURL) err = or.prepare() if err != nil { return err @@ -66,57 +64,50 @@ func (or OakRun) Prepare() error { if err != nil { return err } - log.Infof("prepared new instance OakRun '%s'", lock.DataCurrent().Source) + log.Infof("prepared new instance OakRun '%s'", lock.DataCurrent().DownloadURL) return nil } func (or OakRun) JarFile() string { - return pathx.Abs(fmt.Sprintf("%s/%s", or.Dir(), filepath.Base(or.Source))) + return pathx.Abs(fmt.Sprintf("%s/%s", or.Dir(), filepath.Base(or.DownloadURL))) } func (or OakRun) prepare() error { jarFile := or.JarFile() - if or.Source == OakRunSourceEmbedded { - log.Infof("copying embedded Oak Run JAR to file '%s'", jarFile) - if err := filex.Write(jarFile, instance.OakRunJar); err != nil { - return fmt.Errorf("cannot embedded Oak Run JAR to file '%s': %s", jarFile, err) - } - log.Infof("copied embedded Oak Run JAR to file '%s'", jarFile) - } else { - log.Infof("downloading Oak Run JAR from URL '%s' to file '%s'", or.Source, jarFile) - if err := httpx.DownloadOnce(or.Source, jarFile); err != nil { - return err - } - log.Infof("downloaded Oak Run JAR from URL '%s' to file '%s'", or.Source, jarFile) + log.Infof("downloading Oak Run JAR from URL '%s' to file '%s'", or.DownloadURL, jarFile) + if err := httpx.DownloadOnce(or.DownloadURL, jarFile); err != nil { + return err } + log.Infof("downloaded Oak Run JAR from URL '%s' to file '%s'", or.DownloadURL, jarFile) return nil } func (or OakRun) SetPassword(instanceDir string, user string, password string) error { log.Infof("password setting for user '%s' on instance at dir '%s'", user, instanceDir) - if err := or.RunScript(instanceDir, "set-password", instance.OakRunSetPassword, map[string]any{"User": user, "Password": password}); err != nil { - return err - } - log.Infof("password set for user '%s' on instance at dir '%s'", user, instanceDir) - return nil -} -func (or OakRun) RunScript(instanceDir string, scriptName, scriptTpl string, scriptData map[string]any) error { - scriptContent, err := tplx.RenderString(scriptTpl, scriptData) - if err != nil { + scriptFile := fmt.Sprintf("%s/%s/tmp/oak-run/set-password.groovy", instanceDir, LocalInstanceWorkDirName) + if err := tplx.RenderFile(scriptFile, instance.OakRunSetPassword, map[string]any{"User": user, "Password": password}); err != nil { return err } - scriptFile := fmt.Sprintf("%s/%s/tmp/oak-run/%s.groovy", instanceDir, LocalInstanceWorkDirName, scriptName) - if err := filex.WriteString(scriptFile, scriptContent); err != nil { - return fmt.Errorf("cannot save Oak Run script '%s': %w", scriptFile, err) - } defer func() { pathx.DeleteIfExists(scriptFile) }() + if err := or.RunScript(instanceDir, scriptFile); err != nil { + return err + } + log.Infof("password set for user '%s' on instance at dir '%s'", user, instanceDir) + return nil +} + +func (or OakRun) RunScript(instanceDir string, scriptFile string) error { storeDir := fmt.Sprintf("%s/%s", instanceDir, or.StorePath) // TODO https://issues.apache.org/jira/browse/OAK-5961 (handle JAnsi problem) - cmd := exec.Command("java", "-jar", or.JarFile(), "console", storeDir, "--read-write", fmt.Sprintf(":load %s", scriptFile)) + cmd := exec.Command("java", + "-Djava.io.tmpdir=aem/home/tmp", + "-jar", or.JarFile(), + "console", storeDir, "--read-write", fmt.Sprintf(":load %s", scriptFile), + ) or.localOpts.manager.aem.CommandOutput(cmd) if err := cmd.Run(); err != nil { return fmt.Errorf("cannot run Oak Run script '%s': %w", scriptFile, err) diff --git a/pkg/package_manager.go b/pkg/package_manager.go index c17a7803..ca03ff99 100644 --- a/pkg/package_manager.go +++ b/pkg/package_manager.go @@ -280,8 +280,8 @@ func (pm *PackageManager) deployLock(file string, checksum string) osx.Lock[pack } type packageDeployLock struct { - Deployed time.Time - Checksum string + Deployed time.Time `yaml:"deployed"` + Checksum string `yaml:"checksum"` } func (pm *PackageManager) Uninstall(remotePath string) error { diff --git a/pkg/sdk.go b/pkg/sdk.go index eae741ec..81341fb3 100644 --- a/pkg/sdk.go +++ b/pkg/sdk.go @@ -27,7 +27,7 @@ func (s Sdk) Dir() string { } type SdkLock struct { - Version string + Version string `yaml:"version"` } func (s Sdk) lock(zipFile string) osx.Lock[SdkLock] { diff --git a/pkg/cfg/aem.yml b/project/aem/default/etc/aem.yml old mode 100644 new mode 100755 similarity index 91% rename from pkg/cfg/aem.yml rename to project/aem/default/etc/aem.yml index 0cf41298..dd83ea5e --- a/pkg/cfg/aem.yml +++ b/project/aem/default/etc/aem.yml @@ -11,11 +11,13 @@ instance: user: admin password: admin run_modes: [ local ] + jvm_opts: -Djava.io.tmpdir=aem/home/tmp -Duser.timezone=UTC -Duser.language=en-EN local_publish: http_url: http://127.0.0.1:4503 user: admin password: admin run_modes: [ local ] + jvm_opts: -Djava.io.tmpdir=aem/home/tmp -Duser.timezone=UTC -Duser.language=en-EN # Filters for defined filter: @@ -77,7 +79,8 @@ instance: # Oak Run tool options (offline instance management) oakrun: - source: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.46.0/oak-run-1.46.0.jar" + download_url: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.44.0/oak-run-1.44.0.jar" + store_path: "crx-quickstart/repository/segmentstore" # Source files quickstart: @@ -148,4 +151,4 @@ input: output: format: text - file: aem/home/aem.log + file: aem/home/var/log/aem.log From cf0eb73bd838a1ef4592055dd9c38b481509961d Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Thu, 26 Jan 2023 10:52:02 +0100 Subject: [PATCH 09/11] Init script upgrade --- project/init.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/project/init.sh b/project/init.sh index 432f90bc..5d6f5105 100644 --- a/project/init.sh +++ b/project/init.sh @@ -4,11 +4,15 @@ VERSION=${AEMC_VERSION:-"0.11.2"} SOURCE_URL="https://raw.githubusercontent.com/wttech/aemc/v${VERSION}/project" AEM_WRAPPER="aemw" + AEM_DIR="aem" SCRIPT_DIR="${AEM_DIR}/script" HOME_DIR="${AEM_DIR}/home" +DEFAULT_DIR="${AEM_DIR}/default" +DEFAULT_CONFIG_DIR="${DEFAULT_DIR}/etc" LIB_DIR="${HOME_DIR}/lib" -CONFIG_FILE="${HOME_DIR}/aem.yml" + +CONFIG_FILE="${HOME_DIR}/etc/aem.yml" SETUP_FILE="${SCRIPT_DIR}/setup.sh" if [ -f "$AEM_WRAPPER" ]; then @@ -19,7 +23,9 @@ fi echo "Downloading AEM Compose Files" echo "" -mkdir -p "$SCRIPT_DIR" "$HOME_DIR" +mkdir -p "${SCRIPT_DIR}" "${HOME_DIR}" "${DEFAULT_CONFIG_DIR}" "${LIB_DIR}" + +curl -s "${SOURCE_URL}/${DEFAULT_CONFIG_DIR}/aem.yml" -o "${DEFAULT_CONFIG_DIR}/aem.yml" curl -s "${SOURCE_URL}/${SCRIPT_DIR}/deploy.sh" -o "${SCRIPT_DIR}/deploy.sh" curl -s "${SOURCE_URL}/${SCRIPT_DIR}/destroy.sh" -o "${SCRIPT_DIR}/destroy.sh" curl -s "${SOURCE_URL}/${SCRIPT_DIR}/down.sh" -o "${SCRIPT_DIR}/down.sh" @@ -41,11 +47,6 @@ echo "" ./${AEM_WRAPPER} config init -echo "Creating AEM Compose directories" -echo "" - -mkdir -p "$LIB_DIR" - echo "Initialized AEM Compose" echo "" From 775a7348a0c224d0b7070ccfc09661ba9e8acc2a Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Thu, 26 Jan 2023 11:06:28 +0100 Subject: [PATCH 10/11] Init script redesigned --- cmd/aem/init.go | 25 +++++++++++++++++++++++-- project/init.sh | 28 +++------------------------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/cmd/aem/init.go b/cmd/aem/init.go index a69b360a..d7a8dbdf 100644 --- a/cmd/aem/init.go +++ b/cmd/aem/init.go @@ -1,6 +1,11 @@ package main -import "github.com/spf13/cobra" +import ( + "github.com/spf13/cobra" + "github.com/wttech/aemc/pkg/cfg" + "github.com/wttech/aemc/pkg/common" + "strings" +) func (c *CLI) initCmd() *cobra.Command { cmd := &cobra.Command{ @@ -18,8 +23,24 @@ func (c *CLI) initCmd() *cobra.Command { c.Error(err) return } - c.Ok("initialized properly") + c.SetOutput("gettingStarted", strings.Join([]string{ + "The next step is providing AEM files (JAR or SDK ZIP, license) to directory '" + common.LibDir + "'.", + "Alternatively, instruct the tool where these files are located by adjusting properties: 'dist_file', 'license_file' in configuration file '" + cfg.FileDefault + "'.", + "To avoid problems with IDE performance, make sure to exclude from indexing the directory '" + common.HomeDir + "'.", + "Finally, use control scripts to manage AEM instances:", + "", + + "sh aemw [setup|resetup|up|down|restart]", + + "", + "It is also possible to run individual AEM Compose CLI commands separately.", + "Discover available commands by running:", + "", + + "sh aemw --help", + }, "\n")) + c.Ok("initialized properly") }, } return cmd diff --git a/project/init.sh b/project/init.sh index 5d6f5105..43633772 100644 --- a/project/init.sh +++ b/project/init.sh @@ -12,9 +12,6 @@ DEFAULT_DIR="${AEM_DIR}/default" DEFAULT_CONFIG_DIR="${DEFAULT_DIR}/etc" LIB_DIR="${HOME_DIR}/lib" -CONFIG_FILE="${HOME_DIR}/etc/aem.yml" -SETUP_FILE="${SCRIPT_DIR}/setup.sh" - if [ -f "$AEM_WRAPPER" ]; then echo "The project contains already AEM Compose!" exit 1 @@ -36,32 +33,13 @@ curl -s "${SOURCE_URL}/${SCRIPT_DIR}/up.sh" -o "${SCRIPT_DIR}/up.sh" curl -s "${SOURCE_URL}/${AEM_DIR}/api.sh" -o "${AEM_DIR}/api.sh" curl -s "${SOURCE_URL}/${AEM_WRAPPER}" -o "${AEM_WRAPPER}" -echo "Downloading & Running AEM Compose CLI" +echo "Downloading & Testing AEM Compose CLI" echo "" chmod +x "${AEM_WRAPPER}" sh ${AEM_WRAPPER} version -echo "Scaffolding AEM Compose configuration file" -echo "" - -./${AEM_WRAPPER} config init - -echo "Initialized AEM Compose" -echo "" - -echo "The next step is providing AEM files (JAR or SDK ZIP, license) to directory '${LIB_DIR}'" -echo "Alternatively, instruct the tool where these files are located by adjusting properties: 'dist_file', 'license_file' in configuration file '${CONFIG_FILE}'" -echo "Later on, remember to customise AEM instance setup in provisioning file '${SETUP_FILE}' for service pack installation, application build, etc." -echo "To avoid problems with IDE performance, make sure to exclude from indexing the directory '${HOME_DIR}'" -echo "Finally, use control scripts to manage AEM instances:" -echo "" - -echo "sh aemw [setup|resetup|up|down|restart]" - -echo "" -echo "It is also possible to run individual AEM Compose CLI commands separately." -echo "Discover available commands by running:" +echo "Success! Now initialize AEM Compose by running the command:" echo "" -echo "sh aemw --help" +echo "sh ${AEM_WRAPPER} init" From 5a5383342a82ead4caaf575af83e9ad80b06046a Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Thu, 26 Jan 2023 11:13:34 +0100 Subject: [PATCH 11/11] Clean up --- cmd/aem/config.go | 5 +++-- cmd/aem/init.go | 5 ----- project/aem/default/etc/aem.yml | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/cmd/aem/config.go b/cmd/aem/config.go index 912daa2e..7a56b514 100644 --- a/cmd/aem/config.go +++ b/cmd/aem/config.go @@ -31,8 +31,9 @@ func (c *CLI) configListCmd() *cobra.Command { func (c *CLI) configInitCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "init", - Short: "Initialize configuration", + Use: "initialize", + Aliases: []string{"init"}, + Short: "Initialize configuration", Run: func(cmd *cobra.Command, args []string) { err := c.config.Initialize() if err != nil { diff --git a/cmd/aem/init.go b/cmd/aem/init.go index d7a8dbdf..50186510 100644 --- a/cmd/aem/init.go +++ b/cmd/aem/init.go @@ -19,11 +19,6 @@ func (c *CLI) initCmd() *cobra.Command { return } } - if err := c.aem.InstanceManager().LocalOpts.OakRun.Prepare(); err != nil { - c.Error(err) - return - } - c.SetOutput("gettingStarted", strings.Join([]string{ "The next step is providing AEM files (JAR or SDK ZIP, license) to directory '" + common.LibDir + "'.", "Alternatively, instruct the tool where these files are located by adjusting properties: 'dist_file', 'license_file' in configuration file '" + cfg.FileDefault + "'.", diff --git a/project/aem/default/etc/aem.yml b/project/aem/default/etc/aem.yml index dd83ea5e..119a8339 100755 --- a/project/aem/default/etc/aem.yml +++ b/project/aem/default/etc/aem.yml @@ -73,9 +73,9 @@ instance: # Managed locally (set up automatically) local: # Current runtime dir (Sling launchpad, JCR repository) - unpack_dir: "aem/home/data/instance" + unpack_dir: "aem/home/var/instance" # Archived runtime dir (AEM backup files '*.aemb.zst') - backup_dir: "aem/home/data/backup" + backup_dir: "aem/home/var/backup" # Oak Run tool options (offline instance management) oakrun: