forked from open-horizon/devops
-
Notifications
You must be signed in to change notification settings - Fork 0
/
deploy-mgmt-hub.sh
executable file
·1410 lines (1271 loc) · 68.3 KB
/
deploy-mgmt-hub.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# Deploy the management hub services (agbot, exchange, css, sdo, postgre, mongo), the agent, and the CLI on the current host.
usage() {
exitCode=${1:-0}
cat << EndOfMessage
Usage: ${0##*/} [-c <config-file>] [-A] [-E] [-v] [-h] [-s | -u | -S [-P] | -r <container>]
Deploy the Open Horizon management hub services, agent, and CLI on this host. Currently supports the following operating systems:
* Ubuntu 18.x and 20.x (amd64, ppc64le)
* macOS (experimental)
* RHEL 8.x (ppc64le)
* Note: The support for ppc64le is experimental, because the management hub components are not yet generally available for ppc64le.
Flags:
-c <config-file> A config file with lines in the form variable=value that set any of the environment variables supported by this script. Takes precedence over the same variables passed in through the environment.
-A Do not install the horizon agent package. (It will still install the horizon-cli package.) Without this flag, it will install and register the horizon agent (as well as all of the management hub services).
-R Skip registering the edge node. If -A is not specified, it will install the horizon agent.
-E Skip loading the horizon example services, policies, and patterns.
-S Stop the management hub services and agent (instead of starting them). This flag is necessary instead of you simply running 'docker-compose down' because docker-compose.yml contains environment variables that must be set.
-P Purge (delete) the persistent volumes and images of the Horizon services and uninstall the Horizon agent. Can only be used with -S.
-s Start the management hub services and agent, without installing software or creating configuration. Intended to be run to restart the services and agent at some point after you have stopped them using -S. (If you want to change the configuration, run this script without any flags.)
-u Update any container whose specified version is not currently running.
-r <container> Have docker-compose restart the specified container.
-v Verbose output.
-h Show this usage.
Optional Environment Variables:
For a list of optional environment variables, their defaults and descriptions, see the beginning of this script.
EndOfMessage
exit $exitCode
}
# Get current hardware architecture
export ARCH=$(uname -m | sed -e 's/aarch64.*/arm64/' -e 's/x86_64.*/amd64/' -e 's/armv.*/arm/')
if [[ $ARCH == "ppc64le" ]]; then
export ARCH_DEB=ppc64el
else
export ARCH_DEB="${ARCH}"
fi
# Set the correct default value for docker-compose command regarding to architecture
if [[ $ARCH == "ppc64le" ]]; then
export DOCKER_COMPOSE_CMD="pipenv run docker-compose"
else
export DOCKER_COMPOSE_CMD="docker-compose"
fi
# Parse cmd line
while getopts ":c:ARESPsur:vh" opt; do
case $opt in
c) CONFIG_FILE="$OPTARG"
;;
A) OH_NO_AGENT=1
;;
R) OH_NO_REGISTRATION=1
;;
E) OH_NO_EXAMPLES=1
;;
S) STOP=1
;;
P) PURGE=1
;;
s) START=1
;;
u) UPDATE=1
;;
r) RESTART="$OPTARG"
;;
v) VERBOSE=1
;;
h) usage
;;
\?) echo "Error: invalid option: -$OPTARG"; usage 1
;;
:) echo "Error: option -$OPTARG requires an argument"; usage 1
;;
esac
done
# Read config file, if specified. This will override any corresponding variables from the environment.
# After this, the default values of env vars not set will be set below.
if [[ -n $CONFIG_FILE ]]; then
if [[ ! -f $CONFIG_FILE ]]; then
echo "$CONFIG_FILE does not exist"; exit 1
fi
echo "Reading configuration file $CONFIG_FILE ..."
set -a # export all variable assignments until further notice
source "$CONFIG_FILE"
if [[ $? -ne 0 ]]; then echo "there are errors in $CONFIG_FILE"; exit 1; fi # source seems to return 0 even when there is an error in the file
set +a # undoes the automatic exporting
fi
# Default environment variables that can be overridden. Note: most of them have to be exported for envsubst to use when processing the template files.
# You have the option of specifying the exchange root pw: the clear value is only used in this script temporarily to prime the exchange.
# The bcrypted value can be created using the /admin/hashpw API of an existing exhange. It is stored in the exchange config file, which
# is needed each time the exchange starts. It will default to the clear pw, but that is less secure.
if [[ -z "$EXCHANGE_ROOT_PW" ]];then
if [[ -n "$EXCHANGE_ROOT_PW_BCRYPTED" ]]; then
# Can't specify EXCHANGE_ROOT_PW_BCRYPTED while having this script generate a random EXCHANGE_ROOT_PW, because they won't match
fatal 1 "can not specify EXCHANGE_ROOT_PW_BCRYPTED without also specifying the equivalent EXCHANGE_ROOT_PW"
fi
EXCHANGE_ROOT_PW_GENERATED=1
fi
generateToken() { head -c 1024 /dev/urandom | base64 | tr -cd "[:alpha:][:digit:]" | head -c $1; } # inspired by https://gist.github.com/earthgecko/3089509#gistcomment-3530978
export EXCHANGE_ROOT_PW=${EXCHANGE_ROOT_PW:-$(generateToken 30)} # the clear exchange root pw, used temporarily to prime the exchange
export EXCHANGE_ROOT_PW_BCRYPTED=${EXCHANGE_ROOT_PW_BCRYPTED:-$EXCHANGE_ROOT_PW} # we are not able to bcrypt it, so must default to the clear pw when they do not specify it
# the passwords of the admin user in the system org and of the hub admin. Defaults to a generated value that will be displayed at the end
if [[ -z "$EXCHANGE_SYSTEM_ADMIN_PW" ]]; then
export EXCHANGE_SYSTEM_ADMIN_PW=$(generateToken 30)
EXCHANGE_SYSTEM_ADMIN_PW_GENERATED=1
fi
if [[ -z "$EXCHANGE_HUB_ADMIN_PW" ]]; then
export EXCHANGE_HUB_ADMIN_PW=$(generateToken 30)
EXCHANGE_HUB_ADMIN_PW_GENERATED=1
fi
# the system org agbot token. Defaults to a generated value that will be displayed at the end
if [[ -z "$AGBOT_TOKEN" ]]; then
export AGBOT_TOKEN=$(generateToken 30)
AGBOT_TOKEN_GENERATED=1
fi
# the password of the admin user in the user org. Defaults to a generated value that will be displayed at the end
if [[ -z "$EXCHANGE_USER_ADMIN_PW" ]]; then
export EXCHANGE_USER_ADMIN_PW=$(generateToken 30)
EXCHANGE_USER_ADMIN_PW_GENERATED=1
fi
# the node token. Defaults to a generated value that will be displayed at the end
if [[ -z "$HZN_DEVICE_TOKEN" ]]; then
export HZN_DEVICE_TOKEN=$(generateToken 30)
HZN_DEVICE_TOKEN_GENERATED=1
fi
export HZN_LISTEN_IP=${HZN_LISTEN_IP:-127.0.0.1} # the host IP address the hub services should listen on. Can be set to 0.0.0.0 to mean all interfaces, including the public IP.
# You can also set HZN_LISTEN_PUBLIC_IP to the public IP if you want to set HZN_LISTEN_IP=0.0.0.0 but this script can't determine the public IP.
export HZN_TRANSPORT=${HZN_TRANSPORT:-http} # Note: setting this to https is experimental, still under development!!!!!!
export EXCHANGE_IMAGE_NAME=${EXCHANGE_IMAGE_NAME:-openhorizon/${ARCH}_exchange-api}
export EXCHANGE_IMAGE_TAG=${EXCHANGE_IMAGE_TAG:-latest} # or can be set to stable or a specific version
export EXCHANGE_PORT=${EXCHANGE_PORT:-3090}
export EXCHANGE_LOG_LEVEL=${EXCHANGE_LOG_LEVEL:-INFO}
export EXCHANGE_SYSTEM_ORG=${EXCHANGE_SYSTEM_ORG:-IBM} # the name of the system org (which contains the example services and patterns). Currently this can not be overridden
export EXCHANGE_USER_ORG=${EXCHANGE_USER_ORG:-myorg} # the name of the org which you will use to create nodes, service, patterns, and deployment policies
export EXCHANGE_WAIT_ITERATIONS=${EXCHANGE_WAIT_ITERATIONS:-30}
export EXCHANGE_WAIT_INTERVAL=${EXCHANGE_WAIT_INTERVAL:-2} # number of seconds to sleep between iterations
export AGBOT_IMAGE_NAME=${AGBOT_IMAGE_NAME:-openhorizon/${ARCH}_agbot}
export AGBOT_IMAGE_TAG=${AGBOT_IMAGE_TAG:-latest} # or can be set to stable or a specific version
export AGBOT_ID=${AGBOT_ID:-agbot} # its agbot id in the exchange
export AGBOT_PORT=${AGBOT_PORT:-3110} #todo: should we not expose this to anything but localhost?
export AGBOT_INTERNAL_PORT=${AGBOT_INTERNAL_PORT:-8080}
export AGBOT_SECURE_PORT=${AGBOT_SECURE_PORT:-3111} # the externally accessible port
export AGBOT_INTERNAL_SECURE_PORT=${AGBOT_INTERNAL_SECURE_PORT:-8083}
export ANAX_LOG_LEVEL=${ANAX_LOG_LEVEL:-3} # passed into the agbot containers
# For descriptions for these values in agbot: https://github.com/open-horizon/anax/blob/40bb7c134f7fc5d1699c921489a07b7ec220c89c/config/config.go#L80
export AGBOT_AGREEMENT_TIMEOUT_S=${AGBOT_AGREEMENT_TIMEOUT_S:-360}
export AGBOT_NEW_CONTRACT_INTERVAL_S=${AGBOT_NEW_CONTRACT_INTERVAL_S:-5}
export AGBOT_PROCESS_GOVERNANCE_INTERVAL_S=${AGBOT_PROCESS_GOVERNANCE_INTERVAL_S:-5}
export AGBOT_EXCHANGE_HEARTBEAT=${AGBOT_EXCHANGE_HEARTBEAT:-5}
export AGBOT_CHECK_UPDATED_POLICY_S=${AGBOT_CHECK_UPDATED_POLICY_S:-7}
export AGBOT_AGREEMENT_BATCH_SIZE=${AGBOT_AGREEMENT_BATCH_SIZE:-300}
export AGBOT_RETRY_LOOK_BACK_WINDOW=${AGBOT_RETRY_LOOK_BACK_WINDOW:-3600}
export AGBOT_MMS_GARBAGE_COLLECTION_INTERVAL=${AGBOT_MMS_GARBAGE_COLLECTION_INTERVAL:-20}
# Note: several alternatives were explored for deploying a 2nd agbot:
# - the --scale flag: gave errors about port numbers and container names coonflicting
# - profiles: requires compose schema version 3.9 (1Q2021), docker-compose 1.28, and docker engine 20.10.5 (could switch to this eventually)
# - multiple docker-compose yml files: only include the 2nd one when the 2nd agbot is requested (chose this option)
export START_SECOND_AGBOT=${START_SECOND_AGBOT:-false} # a 2nd agbot is mostly used for e2edev testing
if [[ $START_SECOND_AGBOT == 'true' ]]; then export COMPOSE_FILE='docker-compose.yml:docker-compose-agbot2.yml'; fi # docker-compose will automatically use this
export AGBOT2_PORT=${AGBOT2_PORT:-3120}
export AGBOT2_SECURE_PORT=${AGBOT2_SECURE_PORT:-3121}
export CSS_IMAGE_NAME=${CSS_IMAGE_NAME:-openhorizon/${ARCH}_cloud-sync-service}
export CSS_IMAGE_TAG=${CSS_IMAGE_TAG:-latest} # or can be set to stable or a specific version
export CSS_PORT=${CSS_PORT:-9443} # the host port the css container port should be mapped to
export CSS_INTERNAL_PORT=${CSS_INTERNAL_PORT:-8080} # the port css is listening on inside the container (gets mapped to host port CSS_PORT)
# For descriptions and defaults for these values in CSS: https://github.com/open-horizon/edge-sync-service/blob/master/common/config.go
export CSS_PERSISTENCE_PATH=${CSS_PERSISTENCE_PATH:-/var/edge-sync-service/persist}
export CSS_LOG_LEVEL=${CSS_LOG_LEVEL:-INFO}
export CSS_LOG_TRACE_DESTINATION=${CSS_LOG_TRACE_DESTINATION:-stdout}
export CSS_LOG_ROOT_PATH=${CSS_LOG_ROOT_PATH:-/var/edge-sync-service/log}
export CSS_TRACE_LEVEL=${CSS_TRACE_LEVEL:-INFO}
export CSS_TRACE_ROOT_PATH=${CSS_TRACE_ROOT_PATH:-/var/edge-sync-service/trace}
export CSS_MONGO_AUTH_DB_NAME=${CSS_MONGO_AUTH_DB_NAME:-admin}
export POSTGRES_IMAGE_NAME=${POSTGRES_IMAGE_NAME:-postgres}
export POSTGRES_IMAGE_TAG=${POSTGRES_IMAGE_TAG:-13} # or can be set to stable or a specific version
export POSTGRES_PORT=${POSTGRES_PORT:-5432}
export POSTGRES_USER=${POSTGRES_USER:-admin}
export EXCHANGE_DATABASE=${EXCHANGE_DATABASE:-exchange} # the db the exchange uses in the postgres instance
export AGBOT_DATABASE=${AGBOT_DATABASE:-exchange} #todo: figure out how to get 2 different databases created in postgres. The db the agbot uses in the postgres instance
export MONGO_IMAGE_NAME=${MONGO_IMAGE_NAME:-mongo}
export MONGO_IMAGE_TAG=${MONGO_IMAGE_TAG:-latest} # or can be set to stable or a specific version
export MONGO_PORT=${MONGO_PORT:-27017}
export SDO_IMAGE_NAME=${SDO_IMAGE_NAME:-openhorizon/sdo-owner-services}
export SDO_IMAGE_TAG=${SDO_IMAGE_TAG:-latest} # or can be set to stable, testing, or a specific version
export SDO_OCS_API_PORT=${SDO_OCS_API_PORT:-9008}
export SDO_RV_PORT=${SDO_RV_PORT:-8040}
export SDO_OPS_PORT=${SDO_OPS_PORT:-8042} # the port OPS should listen on *inside* the container
export SDO_OPS_EXTERNAL_PORT=${SDO_OPS_EXTERNAL_PORT:-$SDO_OPS_PORT} # the external port the device should use to contact OPS
export SDO_OCS_DB_PATH=${SDO_OCS_DB_PATH:-/home/sdouser/ocs/config/db}
export SDO_GET_PKGS_FROM=${SDO_GET_PKGS_FROM:-https://github.com/open-horizon/anax/releases/latest/download} # where the SDO container gets the horizon pkgs and agent-install.sh from.
export SDO_GET_CFG_FILE_FROM=${SDO_GET_CFG_FILE_FROM:-css:} # or can be set to 'agent-install.cfg' to use the file SDO creates (which doesn't include HZN_AGBOT_URL)
export EXCHANGE_INTERNAL_RETRIES=${EXCHANGE_INTERNAL_RETRIES:-12} # the maximum number of times to try connecting to the exchange during startup to verify the connection info
export EXCHANGE_INTERNAL_INTERVAL=${EXCHANGE_INTERNAL_INTERVAL:-5} # the number of seconds to wait between attempts to connect to the exchange during startup
# Note: in this environment, we are not supporting letting them specify their own owner key pair (only using the built-in sample key pair)
export VAULT_AUTH_PLUGIN_EXCHANGE=openhorizon-exchange
export VAULT_PORT=${VAULT_PORT:-8200}
export VAULT_DEV_LISTEN_ADDRESS=${VAULT_DEV_LISTEN_ADDRESS:-0.0.0.0:${VAULT_PORT}}
export VAULT_DISABLE_TLS=true
# Todo: Future suuport for TLS/HTTPS with Vault
#if [[ ${HZN_TRANSPORT} == https ]]; then
# VAULT_DISABLE_TLS=false
#else
# VAULT_DISABLE_TLS=true
#fi
export VAULT_IMAGE_NAME=${VAULT_IMAGE_NAME:-openhorizon/${ARCH}_vault}
export VAULT_IMAGE_TAG=${VAULT_IMAGE_TAG:-latest}
export HZN_VAULT_URL=${HZN_TRANSPORT}://${HZN_LISTEN_IP}:${VAULT_PORT}
export VAULT_LOG_LEVEL=${VAULT_LOG_LEVEL:-info}
export VAULT_ROOT_TOKEN=${VAULT_ROOT_TOKEN:-}
export VAULT_SEAL_SECRET_SHARES=1 # Number of keys that exist that are capabale of being used to unseal the vault instance. 0 < shares >= threshold
export VAULT_SEAL_SECRET_THRESHOLD=1 # Number of keys needed to unseal the vault instance. threshold <= shares > 0
export VAULT_SECRETS_ENGINE_NAME=openhorizon
export VAULT_UNSEAL_KEY=${VAULT_UNSEAL_KEY:-}
export AGENT_WAIT_ITERATIONS=${AGENT_WAIT_ITERATIONS:-15}
export AGENT_WAIT_INTERVAL=${AGENT_WAIT_INTERVAL:-2} # number of seconds to sleep between iterations
export COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-hzn}
export HC_DOCKER_TAG=${HC_DOCKER_TAG:-latest} # when using the anax-in-container agent
OH_DEVOPS_REPO=${OH_DEVOPS_REPO:-https://raw.githubusercontent.com/open-horizon/devops/master}
OH_ANAX_RELEASES=${OH_ANAX_RELEASES:-https://github.com/open-horizon/anax/releases/latest/download}
OH_ANAX_MAC_PKG_TAR=${OH_ANAX_MAC_PKG_TAR:-horizon-agent-macos-pkg-x86_64.tar.gz}
OH_ANAX_DEB_PKG_TAR=${OH_ANAX_DEB_PKG_TAR:-horizon-agent-linux-deb-${ARCH_DEB}.tar.gz}
OH_ANAX_RPM_PKG_TAR=${OH_ANAX_RPM_PKG_TAR:-horizon-agent-linux-rpm-${ARCH}.tar.gz}
OH_EXAMPLES_REPO=${OH_EXAMPLES_REPO:-https://raw.githubusercontent.com/open-horizon/examples/master}
HZN_DEVICE_ID=${HZN_DEVICE_ID:-node1} # the edge node id you want to use
# Global variables for this script (not intended to be overridden)
TMP_DIR=/tmp/horizon-all-in-1
mkdir -p $TMP_DIR
CURL_OUTPUT_FILE=$TMP_DIR/curlExchangeOutput
CURL_ERROR_FILE=$TMP_DIR/curlExchangeErrors
VAULT_ERROR_FILE=$TMP_DIR/curlVaultError
VAULT_KEYS_FILE=$TMP_DIR/vaultkeys.json
VAULT_OUTPUT_FILE=$TMP_DIR/curlVaultOutput
VAULT_PLUGIN_FILE=$TMP_DIR/curlVaultPlugin
VAULT_STATUS_FILE=$TMP_DIR/curlVaultStatus
SYSTEM_TYPE=${SYSTEM_TYPE:-$(uname -s)}
DISTRO=${DISTRO:-$(. /etc/os-release 2>/dev/null;echo $ID $VERSION_ID)}
IP_REGEX='^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' # use it like: if [[ $host =~ $IP_REGEX ]]
export CERT_DIR=/etc/horizon/keys
export CERT_BASE_NAME=horizonMgmtHub
export SDO_API_CERT_BASE_NAME=$CERT_BASE_NAME
EXCHANGE_TRUST_STORE_FILE=truststore.p12
# colors for shell ascii output. Must use printf (and add newline) because echo -e is not supported on macos
RED='\e[0;31m'
GREEN='\e[0;32m'
BLUE='\e[0;34m'
PURPLE='\e[0;35m'
CYAN='\e[0;36m'
YELLOW='\e[1;33m'
NC='\e[0m' # no color, return to default
#====================== Functions ======================
# Only echo this if VERBOSE is 1 or true
verbose() {
if [[ "$VERBOSE" == '1' || "$VERBOSE" == 'true' ]]; then
echo 'verbose:' $*
fi
}
# Echo message and exit
fatal() {
local exitCode=$1
# the rest of the args are the message
echo "Error:" ${@:2}
exit $exitCode
}
# Check the exit code passed in and exit if non-zero
chk() {
local exitCode=${1:?}
local task=${2:?}
local dontExit=$3 # set to 'continue' to not exit for this error
if [[ $exitCode == 0 ]]; then return; fi
echo "Error: exit code $exitCode from: $task"
if [[ $dontExit != 'continue' ]]; then
exit $exitCode
fi
}
# Check both the exit code and http code passed in and exit if non-zero
chkHttp() {
local exitCode=${1:?}
local httpCode=${2:?}
local goodHttpCodes=${3:?} # space or comma separated list of acceptable http codes
local task=${4:?}
local errorFile=$5 # optional: the file that has the curl error in it
local outputFile=$6 # optional: the file that has the curl output in it (which sometimes has the error in it)
local dontExit=$7 # optional: set to 'continue' to not exit for this error
if [[ -n $errorFile && -f $errorFile && $(wc -c $errorFile | awk '{print $1}') -gt 0 ]]; then
task="$task, stderr: $(cat $errorFile)"
fi
chk $exitCode $task
if [[ -n $httpCode && $goodHttpCodes == *$httpCode* ]]; then return; fi
# the httpCode was bad, normally in this case the api error msg is in the outputFile
if [[ -n $outputFile && -f $outputFile && $(wc -c $outputFile | awk '{print $1}') -gt 0 ]]; then
task="$task, stdout: $(cat $outputFile)"
fi
echo "Error: http code $httpCode from: $task"
if [[ $dontExit != 'continue' ]]; then
if [[ ! "$httpCode" =~ ^[0-9]+$ ]]; then
httpCode=5 # some times httpCode is the curl error msg
fi
exit $httpCode
fi
}
isMacOS() {
if [[ "$SYSTEM_TYPE" == "Darwin" ]]; then
return 0
else
return 1
fi
}
isUbuntu18() {
if [[ "$DISTRO" == 'ubuntu 18.'* ]]; then
return 0
else
return 1
fi
}
isRedHat8() {
if [[ "$DISTRO" == 'rhel 8.'* ]] && [[ "${ARCH}" == 'ppc64le' ]]; then
return 0
else
return 1
fi
}
isUbuntu20() {
if [[ "$DISTRO" =~ ubuntu\ 2[0-1]\.* ]]; then
return 0
else
return 1
fi
}
isDirInPath() {
local dir=${1:?}
echo $PATH | grep -q -E "(^|:)$dir(:|$)"
}
isWordInString() { # returns true (0) if the specified word is in the space-separated string
local word=${1:?} string=$2
if [[ $string =~ (^|[[:space:]])$word($|[[:space:]]) ]]; then
return 0
else
return 1
fi
}
isDockerContainerRunning() {
local container=${1:?}
if [[ -n $(docker ps -q --filter name=$container) ]]; then
return 0
else
return 1
fi
}
# Run a command that does not have a good quiet option, so we have to capture the output and only show if an error occurs
runCmdQuietly() {
# all of the args to this function are the cmd and its args
if [[ "$VERBOSE" == '1' || "$VERBOSE" == 'true' ]]; then
$*
chk $? "running: $*"
else
local output=$($* 2>&1)
if [[ $? -ne 0 ]]; then
echo "Error running $*: $output"
exit 2
fi
fi
}
# Returns exit code 0 if the specified cmd is in the path
isCmdInstalled() {
local cmd=${1:?}
command -v $cmd >/dev/null 2>&1
local ret=$?
# Special addition for python-based version of docker-compose
if [[ $ret -ne 0 && $cmd == "docker-compose" ]]; then
${DOCKER_COMPOSE_CMD} version --short >/dev/null 2>&1
ret=$?
fi
return $ret
}
# Returns exit code 0 if all of the specified cmds are in the path
areCmdsInstalled() {
for c in $*; do
if ! isCmdInstalled $c; then
return 1
fi
done
return 0
}
# Checks if docker-compose is installed, and if so, if it is at least this minimum version
isDockerComposeAtLeast() {
local minVersion=${1:?}
if ! isCmdInstalled docker-compose; then
return 1 # it is not even installed
fi
# docker-compose is installed, check its version
local lowerVersion=$(echo -e "$(${DOCKER_COMPOSE_CMD} version --short)\n$minVersion" | sort -V | head -n1)
if [[ $lowerVersion == $minVersion ]]; then
return 0 # the installed version was >= minVersion
else
return 1
fi
}
# Verify that the prereq commands we need are installed, or exit with error msg
confirmCmds() {
for c in $*; do
#echo "checking $c..."
if ! isCmdInstalled $c; then
fatal 2 "$c is not installed but required, exiting"
fi
done
}
ensureWeAreRoot() {
if [[ $(whoami) != 'root' ]]; then
fatal 2 "must be root to run ${0##*/}. Run 'sudo -i' and then run ${0##*/}"
fi
}
# Download a file via a URL
getUrlFile() {
local url=${1:?}
local localFile=${2:?}
if isWordInString "${url##*/}" "$OH_DONT_DOWNLOAD"; then
echo "Skipping download of $url"
return
fi
verbose "Downloading $url ..."
if [[ $url == *@* ]]; then
# special case for development:
scp $url $localFile
chk $? "scp'ing $url"
else
local httpCode=$(curl -sS -w "%{http_code}" -L -o $localFile $url 2>$CURL_ERROR_FILE)
chkHttp $? $httpCode 200 "downloading $url" $CURL_ERROR_FILE $localFile
fi
}
# Always pull when the image tag is latest or testing. For other tags, try to pull, but if the image exists locally, but does not exist in the remote repo, do not report error.
pullDockerImage() {
local imagePath=${1:?}
local imageTag=${imagePath##*:}
if [[ $imageTag =~ ^(latest|testing)$ || -z $(docker images -q $imagePath 2> /dev/null) ]]; then
echo "Pulling $imagePath ..."
runCmdQuietly docker pull $imagePath
else
# Docker image exists locally. Try to pull, but only exit if pull fails for a reason other than 'not found'
echo "Trying to pull $imagePath ..."
local output=$(docker pull $imagePath 2>&1)
if [[ $? -ne 0 && $output != *'not found'* ]]; then
echo "Error running docker pull $imagePath: $output"
exit 2
fi
fi
}
# Pull all of the docker images to ensure we have the most recent images locally
pullImages() {
# Even though docker-compose will pull these, it won't pull again if it already has a local copy of the tag but it has been updated on docker hub
pullDockerImage ${AGBOT_IMAGE_NAME}:${AGBOT_IMAGE_TAG}
pullDockerImage ${EXCHANGE_IMAGE_NAME}:${EXCHANGE_IMAGE_TAG}
pullDockerImage ${CSS_IMAGE_NAME}:${CSS_IMAGE_TAG}
pullDockerImage ${POSTGRES_IMAGE_NAME}:${POSTGRES_IMAGE_TAG}
pullDockerImage ${MONGO_IMAGE_NAME}:${MONGO_IMAGE_TAG}
pullDockerImage ${SDO_IMAGE_NAME}:${SDO_IMAGE_TAG}
pullDockerImage ${VAULT_IMAGE_NAME}:${VAULT_IMAGE_TAG}
}
# Find 1 of the private IPs of the host - not currently used
getPrivateIp() {
local ipCmd
if isMacOS; then ipCmd=ifconfig
else ipCmd='ip address'; fi
$ipCmd | grep -m 1 -o -E "\sinet (172|10|192.168)[^/\s]*" | awk '{ print $2 }'
}
# Find 1 of the public IPs of the host
getPublicIp() {
if [[ -n $HZN_LISTEN_PUBLIC_IP ]]; then
echo "$HZN_LISTEN_PUBLIC_IP"
return
fi
local ipCmd
if isMacOS; then ipCmd=ifconfig
else ipCmd='ip address'; fi
$ipCmd | grep -o -E "\sinet [^/\s]*" | grep -m 1 -v -E "\sinet (127|172|10|192.168)" | awk '{ print $2 }'
}
getAllIps() { # get all of the IP addresses and return them as a comma-separated string
ip address | grep -o -E "\sinet [^/\s]*" | awk -vORS=, '{ print $2 }' | sed 's/,$//'
}
# Source the hzn autocomplete file
add_autocomplete() {
local shellFile="${SHELL##*/}"
local autocomplete
if isMacOS; then
local autocomplete="/usr/local/share/horizon/hzn_bash_autocomplete.sh"
# The default terminal app on mac reads .bash_profile instead of .bashrc . But some 3rd part terminal apps read .bashrc, so update that too, if it exists
for rcFile in ~/.${shellFile}_profile ~/.${shellFile}rc; do
if [[ -f "$rcFile" ]]; then
grep -q -E "^source ${autocomplete}" $rcFile 2>/dev/null || echo -e "\nsource ${autocomplete}" >> $rcFile
fi
done
else # linux
local autocomplete="/etc/bash_completion.d/hzn_bash_autocomplete.sh"
grep -q -E "^source ${autocomplete}" ~/.${shellFile}rc 2>/dev/null || echo -e "\nsource ${autocomplete}" >>~/.${shellFile}rc
fi
}
waitForAgent() {
local success
printf "Waiting for the agent to be ready"
for ((i=1; i<=$AGENT_WAIT_ITERATIONS; i++)); do
if $HZN node list >/dev/null 2>$CURL_ERROR_FILE; then
success=true
break
fi
printf '.'
sleep $AGENT_WAIT_INTERVAL
done
echo ''
if [[ "$success" != 'true' ]]; then
local numSeconds=$(( $AGENT_WAIT_ITERATIONS * $AGENT_WAIT_INTERVAL ))
fatal 6 "can not reach the agent (tried for $numSeconds seconds): $(cat $CURL_ERROR_FILE 2>/dev/null)"
fi
}
putOneFileInCss() {
local filename=${1:?} objectID=$2 version=$3 # objectID and version are optional
if [[ -z $objectID ]]; then
objectID=${filename##*/}
fi
echo "Publishing $filename in CSS as public object $objectID in the IBM org..."
echo '{ "objectID":"'$objectID'", "objectType":"agent_files", "destinationOrgID":"IBM", "version":"'$version'", "public":true }' | $HZN mms -o IBM object publish -m- -f $filename
chk $? "publishing $filename in CSS as a public object"
}
isCertForHost() { # Not currently used!! Return true (0) if the current cert is for the specified ip or host.
local ipOrHost=${1:?}
currentCert="$CERT_DIR/$CERT_BASE_NAME.crt"
if [[ ! -f $currentCert ]]; then
return 1 # does not exist
fi
certCommonName=$(openssl x509 -noout -subject -in $currentCert | awk '{print $NF}') # $NF gets the last word of the text
chk $? "getting common name of cert $currentCert"
if [[ $certCommonName == $ipOrHost ]]; then
return 0
else
return 1
fi
}
removeKeyAndCert() {
mkdir -p $CERT_DIR && chmod +r $CERT_DIR # need to make it readable by the non-root user inside the container
rm -f $CERT_DIR/$CERT_BASE_NAME.{key,crt} $CERT_DIR/$EXCHANGE_TRUST_STORE_FILE
chk $? "removing key and cert from $CERT_DIR"
}
createTrustStore() { # Combine the private key and cert into a p12 file for the exchange
echo "Combining the private key and cert into a p12 file for the exchange..."
openssl pkcs12 -export -out $CERT_DIR/$EXCHANGE_TRUST_STORE_FILE -in $CERT_DIR/$CERT_BASE_NAME.crt -inkey $CERT_DIR/$CERT_BASE_NAME.key -aes256 -passout pass:
chk $? "creating $CERT_DIR/$EXCHANGE_TRUST_STORE_FILE"
chmod +r $CERT_DIR/$EXCHANGE_TRUST_STORE_FILE # needed so the exchange container can read it when it is mounted into the container
}
createKeyAndCert() { # create in directory $CERT_DIR a self-signed key and certificate named: $CERT_BASE_NAME.key, $CERT_BASE_NAME.crt
# Check if the cert is already correct from a previous run, so we don't keep changing it
if ! isCmdInstalled openssl; then
fatal 2 "specified HZN_TRANSPORT=$HZN_TRANSPORT, but command openssl is not installed to create the self-signed certificate"
fi
if [[ -f "$CERT_DIR/$CERT_BASE_NAME.key" && -f "$CERT_DIR/$CERT_BASE_NAME.crt" ]]; then
if [[ ! -f $CERT_DIR/$EXCHANGE_TRUST_STORE_FILE ]]; then
createTrustStore # this is the case where they kept the persistent data from a previous version of this script
fi
echo "Certificate $CERT_DIR/$CERT_BASE_NAME.crt already exists, so not receating it"
return # no need to recreate the cert
fi
# Create the private key and certificate that all of the mgmt hub components need
mkdir -p $CERT_DIR && chmod +r $CERT_DIR # need to make it readable by the non-root user inside the container
chk $? "making directory $CERT_DIR"
removeKeyAndCert
local altNames=$(ip address | grep -o -E "\sinet [^/\s]*" | awk -vORS=,IP: '{ print $2 }' | sed -e 's/^/IP:/' -e 's/,IP:$//') # result: IP:127.0.0.1,IP:10.21.42.91,...
altNames="$altNames,DNS:localhost,DNS:agbot,DNS:exchange-api,DNS:css-api,DNS:sdo-owner-services" # add the names the containers use to contact each other
echo "Creating self-signed certificate for these IP addresses: $altNames"
# taken from https://medium.com/@groksrc/create-an-openssl-self-signed-san-cert-in-a-single-command-627fd771f25
openssl req -newkey rsa:4096 -nodes -sha256 -x509 -keyout $CERT_DIR/$CERT_BASE_NAME.key -days 365 -out $CERT_DIR/$CERT_BASE_NAME.crt -subj "/C=US/ST=NY/L=New York/[email protected]/CN=$(hostname)" -extensions san -config <(echo '[req]'; echo 'distinguished_name=req'; echo '[san]'; echo "subjectAltName=$altNames")
chk $? "creating key and certificate"
chmod +r $CERT_DIR/$CERT_BASE_NAME.key
createTrustStore
#todo: should we do this so local curl cmds will use it: ln -s $CERT_DIR/$CERT_BASE_NAME.crt /etc/ssl/certs
}
# ----- Vault functions -----
vaultAuthMethodCheck() {
curl -sS -w "%{http_code}" -o /dev/null -H "X-Vault-Token: $VAULT_ROOT_TOKEN" -H Content-Type:application/json -X GET $HZN_VAULT_URL/v1/sys/auth/$VAULT_SECRETS_ENGINE_NAME/$VAULT_AUTH_PLUGIN_EXCHANGE/tune $* 2>$VAULT_ERROR_FILE
}
vaultCreateSecretsEngine() {
echo Creating KV ver.2 secrets engine $VAULT_SECRETS_ENGINE_NAME...
httpCode=$(curl -sS -w "%{http_code}" -H "X-Vault-Token: $VAULT_ROOT_TOKEN" -H Content-Type:application/json -X POST -d "{\"path\": \"$VAULT_SECRETS_ENGINE_NAME\",\"type\": \"kv\",\"config\": {},\"options\": {\"version\":2},\"generate_signing_key\": true}" $HZN_VAULT_URL/v1/sys/mounts/$VAULT_SECRETS_ENGINE_NAME $* 2>$VAULT_ERROR_FILE)
chkHttp $? $httpCode 204 "vaultCreateSecretsEngine" $VAULT_ERROR_FILE
}
vaultEnableAuthMethod() {
echo Enabling auth method $VAULT_AUTH_PLUGIN_EXCHANGE for secrets engine $VAULT_SECRETS_ENGINE_NAME...
httpCode=$(curl -sS -w "%{http_code}" -H "X-Vault-Token: $VAULT_ROOT_TOKEN" -H Content-Type:application/json -X POST -d "{\"config\": {\"token\": \"$VAULT_ROOT_TOKEN\", \"url\": \"$HZN_TRANSPORT://exchange-api:8080\"}, \"type\": \"$VAULT_AUTH_PLUGIN_EXCHANGE\"}" $HZN_VAULT_URL/v1/sys/auth/$VAULT_SECRETS_ENGINE_NAME)
chkHttp $? $httpCode 204 "vaultEnableAuthMethod" $VAULT_ERROR_FILE
}
vaultPluginCheck() {
curl -sS -w "%{http_code}" -o $VAULT_PLUGIN_FILE -H "X-Vault-Token: $VAULT_ROOT_TOKEN" -H Content-Type:application/json -X GET $HZN_VAULT_URL/v1/sys/plugins/catalog/auth/$VAULT_AUTH_PLUGIN_EXCHANGE $* 2>$VAULT_ERROR_FILE
}
vaultPluginHash() {
echo Generating SHA256 hash of $VAULT_AUTH_PLUGIN_EXCHANGE plugin...
# Note: must redirect stdin to /dev/null, otherwise when this script is being piped into bash the following cmd will gobble the rest of this script and execution will end abruptly
hash=$($DOCKER_COMPOSE_CMD exec -T vault sha256sum /vault/plugins/hznvaultauth </dev/null | cut -d " " -f1)
}
vaultRegisterPlugin() {
local hash=
echo Registering auth plugin $VAULT_AUTH_PLUGIN_EXCHANGE to Vault instance...
vaultPluginHash
httpCode=$(curl -sS -w "%{http_code}" -H "X-Vault-Token: $VAULT_ROOT_TOKEN" -H Content-Type:application/json -X PUT -d "{\"sha256\": \"$hash\", \"command\": \"hznvaultauth\"}" $HZN_VAULT_URL/v1/sys/plugins/catalog/auth/$VAULT_AUTH_PLUGIN_EXCHANGE $* 2>$VAULT_ERROR_FILE)
chkHttp $? $httpCode 204 "vaultRegisterPlugin" $VAULT_ERROR_FILE
}
vaultSecretsEngineCheck() {
curl -sS -w "%{http_code}" -o /dev/null -H "X-Vault-Token: $VAULT_ROOT_TOKEN" -H Content-Type:application/json -X GET $HZN_VAULT_URL/v1/sys/mounts/$VAULT_SECRETS_ENGINE_NAME $* 2>$VAULT_ERROR_FILE
}
vaultServiceCheck() {
echo Checking Vault service status, initialization, and seal...
httpCode=$(curl -sS -w "%{http_code}" -o $VAULT_STATUS_FILE -H Content-Type:application/json -X GET $HZN_VAULT_URL/v1/sys/seal-status $* 2>$VAULT_ERROR_FILE)
chkHttp $? $httpCode 200 "vaultServiceCheck" $VAULT_ERROR_FILE
}
vaultUnregisterPlugin() {
echo Unregistering auth plugin $VAULT_AUTH_PLUGIN_EXCHANGE from Vault instance...
httpCode=$(curl -sS -w "%{http_code}" -H "X-Vault-Token: $VAULT_ROOT_TOKEN" -H Content-Type:application/json -X DELETE $HZN_VAULT_URL/v1/sys/plugins/catalog/auth/$VAULT_AUTH_PLUGIN_EXCHANGE $* 2>$VAULT_ERROR_FILE)
chkHttp $? $httpCode 204 "vaultUnregisterPlugin" $VAULT_ERROR_FILE
}
# Assumes a secret threshold size of 1
vaultUnseal() {
echo Vault instance is sealed. Unsealing...
httpCode=$(curl -sS -w "%{http_code}" -o /dev/null -H Content-Type:application/json -X PUT -d "{\"key\": \"$VAULT_UNSEAL_KEY\"}" $HZN_VAULT_URL/v1/sys/unseal $* 2>$VAULT_ERROR_FILE)
chkHttp $? $httpCode 200 "vaultUnseal" $VAULT_ERROR_FILE
}
vaultInitialize() {
echo A Vault instance has not been initialized. Initializing...
httpCode=$(curl -sS -w "%{http_code}" -o $VAULT_KEYS_FILE -H Content-Type:application/json -X PUT -d "{\"secret_shares\": $VAULT_SEAL_SECRET_SHARES,\"secret_threshold\": $VAULT_SEAL_SECRET_THRESHOLD}" $HZN_VAULT_URL/v1/sys/init $* 2>$VAULT_ERROR_FILE)
chkHttp $? $httpCode 200 "vaultInitialize" $VAULT_ERROR_FILE
VAULT_ROOT_TOKEN=$(cat $VAULT_KEYS_FILE | jq -r '.root_token')
VAULT_UNSEAL_KEY=$(cat $VAULT_KEYS_FILE | jq -r '.keys_base64[0]')
vaultUnseal
vaultCreateSecretsEngine
vaultRegisterPlugin
vaultEnableAuthMethod
}
vaultVaildation() {
echo Found a Vault instance.
# TODO: Regenerated root user's token
#if [[ -z $VAULT_ROOT_TOKEN ]]; then
# VAULT_ROOT_TOKEN=$(cat $VAULT_KEYS_FILE | jq -r '.root_token')
#elif [[ -n $VAULT_ROOT_TOKEN ]] && [[ $VAULT_ROOT_TOKEN != $(cat $VAULT_KEYS_FILE | jq -r '.root_token') ]]; then
# jq -a $VAULT_ROOT_TOKEN '.root_token=$VAULT_ROOT_TOKEN' < $VAULT_KEYS_FILE > $VAULT_KEYS_FILE
#fi
# TODO: Rekeyed the seal of the vault instance
# Will only work if seal was rekeyed to a secret threshold size of 1
#if [[ -z $VAULT_UNSEAL_KEY ]]; then
# VAULT_UNSEAL_KEY=$(cat $VAULT_KEYS_FILE | jq -r '.keys_base64[0]')
#elif [[ -n $VAULT_UNSEAL_KEY ]] && [[ $VAULT_UNSEAL_KEY != $(cat $VAULT_KEYS_FILE | jq -r 'keys_base64[0]') ]]; then
# jq -a $VAULT_UNSEAL_KEY 'keys_base64[0]=$VAULT_ROOT_TOKEN' < $VAULT_KEYS_FILE > $VAULT_KEYS_FILE
#fi
if [[ $(cat $VAULT_STATUS_FILE | jq '.sealed') == true ]]; then
vaultUnseal
fi
if [[ $(vaultSecretsEngineCheck) == 404 ]]; then
vaultCreateSecretsEngine
vaultRegisterPlugin
vaultEnableAuthMethod
elif [[ $(vaultPluginCheck) == 404 ]]; then
vaultRegisterPlugin
vaultEnableAuthMethod
elif [[ $(vaultAuthMethodCheck) == 400 ]]; then
vaultEnableAuthMethod
else
# New Exchange auth plugin
vaultPluginHash
if [[ $hash != $(cat $VAULT_PLUGIN_FILE | jq -r '.data.sha256') ]]; then
echo Found new auth plugin $VAULT_AUTH_PLUGIN_EXCHANGE
vaultUnregisterPlugin
vaultRegisterPlugin
# TODO: Not sure if the auth method needs to be cycled if the plugin has been cycled
#vaultEnableAuthMethod
fi
fi
}
#====================== End of Functions, Start of Main Initialization ======================
# Set distro-dependent variables
if isMacOS; then
HZN=/usr/local/bin/hzn # this is where the mac horizon-cli pkg puts it
export ETC=/private/etc
export VOLUME_MODE=cached # supposedly helps avoid 100% cpu consumption bug https://github.com/docker/for-mac/issues/3499
else # ubuntu and redhat
HZN=hzn # this deb horizon-cli pkg puts it in /usr/bin so it is always in the path
export ETC=/etc
export VOLUME_MODE=ro
fi
# TODO: Future directory for TLS certificates and keys.
#export VAULT_INSTANCE_DIR=${ETC}/vault/file
#export VAULT_KEYS_DIR=${ETC}/vault/keys
# Set OS-dependent package manager settings in Linux
if isUbuntu18 || isUbuntu20; then
export PKG_MNGR=apt-get
export PKG_MNGR_INSTALL_QY_CMD="install -yqf"
export PKG_MNGR_PURGE_CMD="purge -yq"
export PKG_MNGR_GETTEXT="gettext-base"
else # redhat
export PKG_MNGR=dnf
export PKG_MNGR_INSTALL_QY_CMD="install -y -q"
export PKG_MNGR_PURGE_CMD="erase -y -q"
export PKG_MNGR_GETTEXT="gettext"
fi
# Initial checking of the input and OS
if [[ -z "$EXCHANGE_ROOT_PW" || -z "$EXCHANGE_ROOT_PW_BCRYPTED" ]]; then
fatal 1 "these environment variables must be set: EXCHANGE_ROOT_PW, EXCHANGE_ROOT_PW_BCRYPTED"
fi
if [[ ! $HZN_LISTEN_IP =~ $IP_REGEX ]]; then
fatal 1 "HZN_LISTEN_IP must be an IP address (not a hostname)"
fi
ensureWeAreRoot
if ! isMacOS && ! isUbuntu18 && ! isUbuntu20 && ! isRedHat8; then
fatal 1 "the host must be Ubuntu 18.x (amd64, ppc64le) or Ubuntu 20.x (amd64, ppc64le) or macOS or RedHat 8.x (ppc64le)"
fi
printf "${CYAN}------- Checking input and initializing...${NC}\n"
confirmCmds grep awk curl # these should be automatically available on all the OSes we support
echo "Management hub services will listen on ${HZN_TRANSPORT}://$HZN_LISTEN_IP"
# Install jq envsubst (gettext-base) docker docker-compose
if isMacOS; then
# we can't install docker* for them
if ! isCmdInstalled docker || ! isCmdInstalled docker-compose; then
fatal 2 "you must install docker before running this script: https://docs.docker.com/docker-for-mac/install"
fi
if ! areCmdsInstalled jq envsubst socat; then
fatal 2 "these commands are required: jq, envsubst (installed via the gettext package), socat. Install them via https://brew.sh/ or https://www.macports.org/ ."
fi
else # ubuntu and redhat
echo "Updating ${PKG_MNGR} package index..."
runCmdQuietly ${PKG_MNGR} update -q -y
echo "Installing prerequisites, this could take a minute..."
if [[ $HZN_TRANSPORT == 'https' ]]; then
optionalOpensslPkg='openssl'
fi
runCmdQuietly ${PKG_MNGR} ${PKG_MNGR_INSTALL_QY_CMD} jq ${PKG_MNGR_GETTEXT} make $optionalOpensslPkg
# If docker isn't installed, do that
if ! isCmdInstalled docker; then
echo "Docker is required, installing it..."
if isUbuntu18 || isUbuntu20; then
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
chk $? 'adding docker repository key'
add-apt-repository "deb [arch=${ARCH_DEB}] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
chk $? 'adding docker repository'
if [[ $ARCH == "amd64" ]]; then
${PKG_MNGR} install -y docker-ce docker-ce-cli containerd.io
elif [[ $ARCH == "ppc64le" ]]; then
if isUbuntu18; then
${PKG_MNGR} install -y docker-ce containerd.io
else # Ubuntu 20
${PKG_MNGR} install -y docker.io containerd
fi
else
fatal 1 "hardware plarform ${ARCH} is not supported yet"
fi
chk $? 'installing docker'
else # redhat (ppc64le)
OP_REPO_ID="Open-Power"
IS_OP_REPO_ID=$(${PKG_MNGR} repolist ${OP_REPO_ID} | grep ${OP_REPO_ID} | cut -d" " -f1)
if [[ "${IS_OP_REPO_ID}" != "${OP_REPO_ID}" ]]; then
# Add OpenPower repo with ID Open-Power
cat > /etc/yum.repos.d/open-power.repo << EOFREPO
[Open-Power]
name=Unicamp OpenPower Lab - $basearch
baseurl=https://oplab9.parqtec.unicamp.br/pub/repository/rpm/
enabled=1
gpgcheck=0
repo_gpgcheck=1
gpgkey=https://oplab9.parqtec.unicamp.br/pub/key/openpower-gpgkey-public.asc
EOFREPO
runCmdQuietly ${PKG_MNGR} update -q -y
fi
${PKG_MNGR} install -y docker-ce docker-ce-cli containerd
chk $? 'installing docker'
systemctl --now --quiet enable docker
chk $? 'starting docker'
fi
fi
minVersion=1.21.0
if ! isDockerComposeAtLeast $minVersion; then
if isCmdInstalled docker-compose; then
fatal 2 "Need at least docker-compose $minVersion. A down-level version is currently installed, preventing us from installing the latest version. Uninstall docker-compose and rerun this script."
fi
echo "docker-compose is not installed or not at least version $minVersion, installing/upgrading it..."
if [[ "${ARCH}" == "amd64" ]]; then
# Install docker-compose from its github repo, because that is the only way to get a recent enough version
curl --progress-bar -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chk $? 'downloading docker-compose'
chmod +x /usr/local/bin/docker-compose
chk $? 'making docker-compose executable'
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
chk $? 'linking docker-compose to /usr/bin'
export DOCKER_COMPOSE_CMD="docker-compose"
elif [[ "${ARCH}" == "ppc64le" ]]; then
# Install docker-compose for ppc64le platform (python-based)
${PKG_MNGR} install -y python3 python3-pip
chk $? 'installing python3 and pip'
pip3 install pipenv
chk $? 'installing pipenv'
# Install specific version of docker-compose because the latest one is not working just now (possible reason see on https://status.python.org)
pipenv install docker-compose==$minVersion
chk $? 'installing python-based docker-compose'
export DOCKER_COMPOSE_CMD="pipenv run docker-compose"
else
fatal 1 "hardware plarform ${ARCH} is not supported yet"
fi
fi
fi
# Create self-signed certificate (if necessary)
if [[ $HZN_TRANSPORT == 'https' ]]; then
if isMacOS; then
fatal 1 "Using HZN_TRANSPORT=https is not supported on macOS"
fi
createKeyAndCert # this won't recreate it if already correct
# agbot-tmpl.json can only have these set when using https
export SECURE_API_SERVER_KEY="/home/agbotuser/keys/${CERT_BASE_NAME}.key"
export SECURE_API_SERVER_CERT="/home/agbotuser/keys/${CERT_BASE_NAME}.crt"
export EXCHANGE_HTTP_PORT=8081 #todo: change this back to null when https://github.com/open-horizon/anax/issues/2628 is fixed. Just for CSS.
export EXCHANGE_HTTPS_PORT=8080 # the internal port it listens on
export EXCHANGE_TRUST_STORE_PATH=\"/etc/horizon/exchange/keys/${EXCHANGE_TRUST_STORE_FILE}\" # the exchange container's internal path
EXCH_CERT_ARG="--cacert $CERT_DIR/$CERT_BASE_NAME.crt" # for use when this script is calling the exchange
export CSS_LISTENING_TYPE=secure
export HZN_MGMT_HUB_CERT=$(cat $CERT_DIR/$CERT_BASE_NAME.crt) # for sdo ocs-api to be able to contact the exchange
else
removeKeyAndCert # so when we mount CERT_DIR to the containers it will be empty
export CSS_LISTENING_TYPE=unsecure
export EXCHANGE_HTTP_PORT=8080 # the internal port it listens on
export EXCHANGE_HTTPS_PORT=null
export EXCHANGE_TRUST_STORE_PATH=null
export HZN_MGMT_HUB_CERT='' # needs to be in the environment or docker-compose will complain
fi
# Download and process templates from open-horizon/devops
printf "${CYAN}------- Downloading template files...${NC}\n"
getUrlFile $OH_DEVOPS_REPO/mgmt-hub/docker-compose.yml docker-compose.yml
getUrlFile $OH_DEVOPS_REPO/mgmt-hub/docker-compose-agbot2.yml docker-compose-agbot2.yml
getUrlFile $OH_DEVOPS_REPO/mgmt-hub/exchange-tmpl.json $TMP_DIR/exchange-tmpl.json
getUrlFile $OH_DEVOPS_REPO/mgmt-hub/agbot-tmpl.json $TMP_DIR/agbot-tmpl.json
getUrlFile $OH_DEVOPS_REPO/mgmt-hub/css-tmpl.conf $TMP_DIR/css-tmpl.conf
getUrlFile $OH_DEVOPS_REPO/mgmt-hub/vault-tmpl.json $TMP_DIR/vault-tmpl.json
# Leave a copy of ourself in the current dir for subsequent stop/start commands.
# If they are running us via ./deploy-mgmt-hub.sh we can't overwrite ourselves (or we get syntax errors), so only do it if we are piped into bash or for some other reason aren't executing the script from the current dir
if [[ $0 == 'bash' || ! -f deploy-mgmt-hub.sh ]]; then
getUrlFile $OH_DEVOPS_REPO/mgmt-hub/deploy-mgmt-hub.sh deploy-mgmt-hub.sh
chmod +x deploy-mgmt-hub.sh
fi
# also leave a copy of test-mgmt-hub.sh and test-sdo.sh so they can run those afterward, if they want
getUrlFile $OH_DEVOPS_REPO/mgmt-hub/test-mgmt-hub.sh test-mgmt-hub.sh
chmod +x test-mgmt-hub.sh
getUrlFile $OH_DEVOPS_REPO/mgmt-hub/test-sdo.sh test-sdo.sh
chmod +x test-sdo.sh
echo "Substituting environment variables into template files..."
export ENVSUBST_DOLLAR_SIGN='$' # needed for essentially escaping $, because we need to let the exchange itself replace $EXCHANGE_ROOT_PW_BCRYPTED
mkdir -p /etc/horizon # putting the config files here because they are mounted long-term into the containers
cat $TMP_DIR/exchange-tmpl.json | envsubst > /etc/horizon/exchange.json
cat $TMP_DIR/agbot-tmpl.json | envsubst > /etc/horizon/agbot.json
cat $TMP_DIR/css-tmpl.conf | envsubst > /etc/horizon/css.conf
export VAULT_LOCAL_CONFIG=$(cat $TMP_DIR/vault-tmpl.json | envsubst)
#====================== Start/Stop/Restart/Update ======================
# Special cases to start/stop/restart via docker-compose needed so all of the same env vars referenced in docker-compose.yml will be set
# Check for invalid flag combinations
if [[ $(( ${START:-0} + ${STOP:-0} + ${UPDATE:-0} )) -gt 1 ]]; then
fatal 1 "only 1 of these flags can be specified: -s, -S, -u"
fi
if [[ -n "$PURGE" && -z "$STOP" ]]; then
fatal 1 "-p can only be used with -S"
fi
# Bring down the agent and the mgmt hub services
if [[ -n "$STOP" ]]; then
printf "${CYAN}------- Stopping Horizon services...${NC}\n"
# Unregister if necessary
if [[ $($HZN node list 2>&1 | jq -r '.configstate.state' 2>&1) == 'configured' ]]; then
$HZN unregister -f
chk $? 'unregistration'
fi
if isMacOS; then
if [[ -z $OH_NO_AGENT ]]; then
/usr/local/bin/horizon-container stop
fi
if [[ -n "$PURGE" ]]; then
echo "Uninstalling the Horizon CLI..."
/usr/local/bin/horizon-cli-uninstall.sh -y # removes the content of the horizon-cli pkg
if [[ -z $OH_NO_AGENT ]]; then
echo "Removing the Horizon agent image..."
runCmdQuietly docker rmi openhorizon/amd64_anax:$HC_DOCKER_TAG
fi
fi
elif [[ -z $OH_NO_AGENT ]]; then # ubuntu and redhat
echo "Stopping the Horizon agent..."
systemctl stop horizon
if [[ -n "$PURGE" ]]; then
echo "Uninstalling the Horizon agent and CLI..."
runCmdQuietly ${PKG_MNGR} ${PKG_MNGR_PURGE_CMD} horizon horizon-cli
fi
else # ubuntu and redhat, but only cli
if [[ -n "$PURGE" ]]; then
echo "Uninstalling the Horizon CLI..."
runCmdQuietly ${PKG_MNGR} ${PKG_MNGR_PURGE_CMD} horizon-cli
fi
fi
if [[ -n "$PURGE" ]]; then
echo "Stopping Horizon management hub services and deleting their persistent volumes..."
purgeFlag='--volumes'
else
echo "Stopping Horizon management hub services..."
fi
${DOCKER_COMPOSE_CMD} down $purgeFlag
if [[ -n "$PURGE" ]]; then
removeKeyAndCert