diff --git a/docker-compose.tmpl b/docker-compose.tmpl new file mode 100644 index 0000000..c90dedb --- /dev/null +++ b/docker-compose.tmpl @@ -0,0 +1,64 @@ +version: "3" + +services: + {{if index .InstallComponents "Core"}} + redis: + image: redis:5 + + database: + image: postgres:12-alpine + environment: + - POSTGRES_EXTENSIONS=citext + - POSTGRES_HOST_AUTH_METHOD=trust + {{end}} + {{if index .InstallComponents "Shell"}} + shell: + image: ractf/shell + ports: + - "8000:8000" + environment: + - RACTF_API_BASE=/api/v2 + - RACTF_USE_HEAD_NAV=true + - RACTF_SITE_NAME={{.EventName}} + - RACTF_API_DOMAIN=https://{{.APIDomain}} + - RACTF_WSS_URL=wss://{{.APIDomain}}/api/v2/ws/ + {{if index .InstallComponents "Core"}} + depends_on: + - backend + - sockets + {{end}} + {{end}} + {{if index .InstallComponents "Core"}} + backend: &backend + image: ractf/core + entrypoint: /app/entrypoints/backend.sh + command: gunicorn -w 12 -b 0.0.0.0:8000 backend.wsgi:application + environment: + - LOAD_FIXTURES=0 + - FRONTEND_URL={{.FrontendURL}} + - SECRET_KEY={{.SecretKey}} + - DJANGO_SETTINGS_MODULE=backend.settings.local + + - ANDROMEDA_IP=andromeda + - ANDROMEDA_URL=http://andromeda:6000 + + - REDIS_PORT=6379 + - REDIS_HOST=redis + - REDIS_CONFIG_DB=3 + - REDIS_CACHE_DB=10 + + - SQL_PORT=5432 + - SQL_HOST=database + - SQL_USER=postgres + - SQL_DATABASE=postgres + + depends_on: + - database + + sockets: + <<: *backend + entrypoint: /app/entrypoints/sockets.sh + command: daphne -b 0.0.0.0 -p 8000 backend.asgi:application + depends_on: + - backend + {{end}} \ No newline at end of file diff --git a/go.mod b/go.mod index d5f5e94..96c523a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,9 @@ module github.com/ractf/install go 1.14 + +require ( + github.com/logrusorgru/aurora v2.0.3+incompatible + github.com/manifoldco/promptui v0.7.0 + github.com/markbates/pkger v0.17.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0a70dd3 --- /dev/null +++ b/go.sum @@ -0,0 +1,43 @@ +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/manifoldco/promptui v0.7.0 h1:3l11YT8tm9MnwGFQ4kETwkzpAwY2Jt9lCrumCUW4+z4= +github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= +github.com/markbates/pkger v0.17.0 h1:RFfyBPufP2V6cddUyyEVSHBpaAnM1WzaMNyqomeT+iY= +github.com/markbates/pkger v0.17.0/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 8951446..6f908b2 100644 --- a/main.go +++ b/main.go @@ -1,58 +1,114 @@ package main import ( + "errors" "fmt" - . "github.com/logrusorgru/aurora" - "github.com/manifoldco/promptui" + "github.com/markbates/pkger" + "io/ioutil" + "os" + "strings" + "text/template" ) +type options struct { + EventName string + InstallComponents map[string]bool + SecretKey string + FrontendURL string + APIDomain string +} + func main() { fmt.Println(Cyan("Welcome to the"), Bold("RACTF"), Cyan("setup script")) - selectedComponents := cumulativeSelect("Which services would you like to install?", []string{"Andromeda", "Core", "Shell", "Confirm"}) + installOptions := options{} - if len(selectedComponents) == 0 { - fmt.Println(Red("You must select at least one component to continue.")) + installOptions.InstallComponents = cumulativeSelect("Which services would you like to install?", []string{"Andromeda", "Core", "Shell"}) + + var installCount int + for _, v := range installOptions.InstallComponents { + if v { + installCount += 1 + } + } + + if installCount == 0 { + fmt.Println(Red("You must select at least one service to continue.")) + return + } + if installOptions.InstallComponents["Andromeda"] { + fmt.Println(Red("Andromeda install is not currently supported by this script.")) return } -} -func cumulativeSelect(prompt string, items []string) []string { - var selected []string + if installOptions.InstallComponents["Shell"] { + installOptions.EventName = promptString("What's the (short) name of your event (e.g. RACTF)?", stringValidator) + installOptions.APIDomain = promptString("What's the public URL of your API? (Don't include http(s) or a trailing slash, include a port if necessary)", partialDomainValidator) + } - for index := 0; ; { - prompt := promptui.Select{ - Label: fmt.Sprintf("%s (Currently selected: %s)", Yellow(prompt), selected), - Items: items, - HideSelected: true, - CursorPos: index, - } + if installOptions.InstallComponents["Core"] { + installOptions.FrontendURL = promptString("What URL will visitors access your site through? (Include http(s) and a trailing /)", fullDomainValidator) + } - index, choice, err := prompt.Run() + installOptions.SecretKey = GenerateRandomString(64) - if err != nil { - fmt.Println("Prompt failed to display.") - break - } + fmt.Println(Green("Proceeding with installation of"), Bold(installCount), Green("components.")) + generateAndWriteDockerFile(installOptions) +} - if index == 3 { - break - } +func generateAndWriteDockerFile(options options) { + tf, err := pkger.Open("/docker-compose.tmpl") + if err != nil { + fmt.Println(err) + } + templ, err := ioutil.ReadAll(tf) + if err != nil { + fmt.Println(err) + } - var removedFromSelected bool + t, err := template.New("dockerCompose").Parse(string(templ)) + if err != nil { + fmt.Println(err) + return + } - for i, val := range selected { - if val == choice { - selected = append(selected[:i], selected[i+1:]...) - removedFromSelected = true - break - } - } - if !removedFromSelected { - selected = append(selected, choice) - } + f, err := os.Create("docker-compose.yaml") + if err != nil { + fmt.Println("create file: ", err) + return + } + + err = t.Execute(f, options) + if err != nil { + fmt.Println(err) + return + } +} + +func stringValidator(input string) error { + if len(input) == 0 { + return errors.New("input must be longer than one char") } + return nil +} - return selected +func partialDomainValidator(input string) error { + if strings.HasPrefix(input, "http") { + return errors.New("string should not start with http") + } + if strings.HasSuffix(input, "/") { + return errors.New("string should not end with /") + } + return nil +} + +func fullDomainValidator(input string) error { + if !strings.HasPrefix(input, "http") { + return errors.New("string should start with http") + } + if !strings.HasSuffix(input, "/") { + return errors.New("string should end with /") + } + return nil } diff --git a/pkged.go b/pkged.go new file mode 100644 index 0000000..395a36f --- /dev/null +++ b/pkged.go @@ -0,0 +1,12 @@ +// Code generated by pkger; DO NOT EDIT. + +// +build !skippkger + +package main + +import ( + "github.com/markbates/pkger" + "github.com/markbates/pkger/pkging/mem" +) + +var _ = pkger.Apply(mem.UnmarshalEmbed([]byte(`1f8b08000000000000ffec585993e2ba92fe2b3714f336747901536522e681cd2c5d5085f102bed1d121dbf252c896c79229e044fff709194399dacf9db7133c8033539fd2a95466caa9bf409c068482ce5f208c5954b8371e49841c7a2c10e2943288311f1bc439e880ff1a3fcc8627f179b80126494672f60859043a1fab6980394c10e88004c629688001f14007800630601e22c6f58f1e1ebbc65870e3b4364b27847df2f219645e043aff0637e057032c19c40874585ea08ad111a424051dc027a07ff92843a98f526fdff957cd544cc2bca0240ff34280454e72081a6044b41823ca95739b6f42021a20db84c8afc89c2419fb5db018d3a32487a95fe7cfe4af93974a6d5ebecf1811381a3400ca739253d00041c240037c65556d3c81691c10ec7b4438da52c4af01f9c6850c51819b9d7f3ac8ffe334141294800620dc20caf2380d39c5d08e090c2519860cf1d50c50562ec52d8298afd4dd33c4811e49b21c512a0425b226080f7156f22983718a7201c7945502b42ba9d22d674280478d47c68bb3a85c41c5fbf5419fc217067997ac2f2b8aa4be110871ca509e422c20ff19e63e7d0dc338ce58ecbd48a204d6b8f374be8d7ca3df19a285cb307a19487ce585a9b6ffc479ad1a535f008da074c1c94afb825724b9c6bf7a25c3353fed1451bde4846c13ef7810a61ef1e334ac9102a4a954e75d4851bb7521895398efeb9208d5b5094f3cf76a7c5606d7c711ef45873dca851c411fc729ba1c0c895b0401c4448850fe6aeca9782a0498d298a13cf9644860d07dce39f9f7520d1769bc7389175161cb3c8c60fab753f10de094015fa0a8972394ba45f0ffc8edb70e7b8d38c76c0233fa4d28831b4452e15d6884b237d5ea3f542304316628ffc2aa63f9fa568943898bfcbf510cbfc651e69337f631960a21f9e1114c72e8e2371b508dc71432c693288234aa1e82977b4d9ed56727f1020b7158177959516783845192b3ba28458ce5d0437519a165fad54519298fd25a45bb9c92a300238fe1985d88699c861805380ea38bb7d23df5f821cd53a62e6788324cca2590f24f884955388fe2847f421c1f821b872f24a327ba2a9a499ca0ea212405667106cb959782ff2d08437e96c729abdc9e2276fc1722c6b21a59fe9d5c74169e2cae64fc78ca72521e4d9c2f727c3a210915d00e79478ac6e17152765c077ff0d845155fb9b1a442b4cbce8440f72983dc577991b2e3d22a4af08e1f161577f625642429cfa637239513dfc8e9be3cd28f114259ee91747b71c6d37dea558f17f5d55ebef90478c50b19cc69293d5a5fa4b147fc1a25142c90da97fc5dc9521870dc16a53ec9859060988637240f859d703afa23e84550163382f7525354be40970f9e2edfc59d0ebacfc045be45a70f844f70d1c60f3e47bc646fb5aa6fc2cfe7fa27e02f1cc463d74f29ff258852187ea4ee223bc2a2ccbe2f71594e76fb2f80b21065d0db7c828afd147e304cf7b42a79ef8d96e1489157e44870633fce8b0fbd758cdc1ca63420e5c7c287a053b07285dfc1a55cdfaf06301065e7ee212d303e8ace2dc05134233e37b2f317f8ba779af1a6a9ea6b3eefc6466446fc8fc68590dc24c42f6116ca695cb646d28dd4027ffefc6980e068f2c7dd6047f089b741f90ffe5d4f28ba614956b688bc8fe44f1f3118e352477aecf7de9bd000343e20d09194a6d200092f159d56b35d92bfcb1ad201b2288b3fc4bb1f926848ed8ea27664e9a6a5b4c556fb566afdb72875449e3b31fded737f04105354d6abb261455bd069b76edb6a034c52023ad25d5b54c45603cc719c6e40472add8f40a7d994b9d88c7dd09144b1d900a31772f5fb77067d1174c406d07dae536c8065cdf21ede1c17d212d5366789b7a1a073d7005d1627dc9225f23858bd156f1559561a604eb9a47577d7be55efda777f1a60f60e546a4b67e869cd7f1aa0ff7de8eaf7ef222d28f241e7df62436c88bfca1de64dd4b5a3bf76f4d78efedad15f3bfa6b477fede8af1dfdb5a3bf76f4d78efedad15f3bfa7f58475f152e6ecf26fc562bf76e7bffa7017cc820efe5136befc978eb3e9170f2348bfb617670565319da73ec1dc8cf49bf778b649c4c46b87046566bd257a66e3a17d7f68e2e64957923b5706c45f4f6dd7821ab7b67395583050927e3297646f8f0109270d2ef86d096224736db67f993f9b31f774367a4896b7b1a79b2d9e6fca4dfcb5c5b4b1d8384de483df8237fefac66ed9931616b7bf70c6d055738eca64ee6256ae1da56e10fb8adddf25df7cbdec290e6a62e4d355352355bd33503cfa786a8188ff63cf34756cbef1eed9af4bb6ca2f51e4d491f9b1bcb588911a7838565994b4d9deb963e31c485ea8fa792d75c54feb00a672ca9dc0ed4649913f73237d131ea778ba5ad1cfc9146dde17cebae7a5b37b10a7f3c0b27784e1c7b4727e9719e2747d81d3dbf5d73a23df9a3f5b61a3fae69dcdb7aa97ef66569737fd29a0dbacf0f4fe1f36cd08dbfe193e5c29c9b3a567be610078b8d66e886b85daf7ad97d73bd3fcee73f313437dac01a3a8165cdb59518690b530f8c8d663faef4bd6f9befe9349696aead44a56798a68a9ab4d0570e7653fd616d4b3858891fdaa10fd5f9c2c40f8f7624fae3dee121bedbf2f90bab37d5472a5bdbb8f860feca94e681654d678f2bffe01dc8f6bec96eef37da6269ea5bd7d6323795d47b597b864b559ec56ad3dbdf55fe64b7d05e87d05636ce2a0cef37b8f09a7ae48e760357969e5d59e1b61f26fde9c06d4ef169cf26fddec659f5b09be88795ac1617fb319a466b99613759d46d3db8f23c7756faa19aff3a76b8ae2a5ffe339bd689f604b9ce0109a7c92b1b4698ad6d1f3fc4bdfdda9e8b4eac3ef1b92fb1a2efd1aab785b6223ec45d1e0bcff7328fd7a9e28dd48cbfef5e3eeb2c3c39acf4ceb7ae2d456eb2684f46bee42698eb2d267da939190cf793be144f06dd62d66f3ddf3f75db0f83eef3acdf8bd7f63c776c65739ffa0747deb4f9fbdc117e5aaff4cc955b956eabf013bc776585f1ba72e1e3e1ee91c7a2bec16b4bb396ba355367f53c1e3a4b43544cdd54862bc95a1a03e916ed9511cf077f64158e662d8df1692fab5817ad81b9b1cc95c834db28f18663cff7ce4abf7756f8b83767fdfacf85a98c0d4935744b3797a6323625756e88ba650c4df5bc177de5e0ac7411da4aeaed15eacaf3c8ed5ee462cfd8e84b4394347da8054bababae6d65e3252a736c3daadbb73095a1b951e7baa9f7aa35117fac3ff33ca9cf79785af33af0f3328fade1d29a07e6505d5a03519e1d3ce5bdf1e55035ac81b4776c3df39eeb7571aae9436cacc4f9a3b171a6baa40e174fe2e13d1d0b511b2c8756a00f27eacc786587a4cd5652efd1c40b756e2c0eb3fa9e6a73ddd0d409afd78fb61ef9232d5eafe6f8ed7ccbd0ad895a3b13eafe344c7317e843cd5c98d39e299a17b8af73b7f65e2e1fcfb73ce6fdf1acaacfddbbc701097fbec9af7aae2cda93be1a79e3eeb63c0f53fcecca98d7fead27ab3c2e446faf1c6015af6b5965aead9579eb8cb4673852f0abbc09ef57ddf04dee24da27b9a3638fc7f9781eb872ab5df7e13936bbc75ac375052bf17fca1e2847297bf9a678f97600a70b6674bd61bede305f6f98af37ccd71be6eb0df3f586f97ac37cbd61bede305f6f98af37ccd71be67fda0df39fff030000ffff010000ffff7260252f42340000`))) diff --git a/prompt_utils.go b/prompt_utils.go new file mode 100644 index 0000000..669406a --- /dev/null +++ b/prompt_utils.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + . "github.com/logrusorgru/aurora" + "github.com/manifoldco/promptui" + "strings" +) + +func promptString(promptMessage string, validate promptui.ValidateFunc) string { + prompt := promptui.Prompt{ + Label: fmt.Sprintf("%s", Yellow(promptMessage)), + Validate: validate, + } + + result, err := prompt.Run() + + if err != nil { + fmt.Println(Red("Prompt failed to display.")) + return "" + } + + return result +} + +func cumulativeSelect(prompt string, items []string) map[string]bool { + selected := make(map[string]bool) + for _, v := range items { + selected[v] = false + } + + items = append(items, "Confirm") + + for { + var enabledList []string + for i, v := range selected { + if v { + enabledList = append(enabledList, i) + } + } + if len(enabledList) == 0 { + enabledList = []string{"[None]"} + } + + prompt := promptui.Select{ + Label: fmt.Sprintf("%s (Currently selected: %s)", Yellow(prompt), strings.Join(enabledList, ", ")), + Items: items, + HideSelected: true, + } + + index, choice, err := prompt.Run() + + if err != nil { + fmt.Println(Red("Prompt failed to display.")) + break + } + + if index == len(items)-1 { + break + } + + selected[choice] = !selected[choice] + } + + return selected +} diff --git a/rand_utils.go b/rand_utils.go new file mode 100644 index 0000000..34a87f2 --- /dev/null +++ b/rand_utils.go @@ -0,0 +1,25 @@ +package main + +import "crypto/rand" + +func GenerateRandomBytes(n int) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + if err != nil { + return nil, err + } + + return b, nil +} + +func GenerateRandomString(n int) string { + const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" + bytes, err := GenerateRandomBytes(n) + if err != nil { + return "" + } + for i, b := range bytes { + bytes[i] = letters[b%byte(len(letters))] + } + return string(bytes) +}