123456789module cli1011import json12import os13import src.fmt14import src.machine15import src.remote1617pub fn cmd_remote(raw []string) int {18 args := parse('remote', raw, Options{19 flags: ['json', 'forget']20 }) or { return usage_error(err.msg()) }21 if args.flag('forget') {22 if !machine.has_remote() {23 return refuse(args.json(), 'this machine has no remote to forget')24 }25 machine.forget_remote() or { return refuse(args.json(), err.msg()) }26 if args.json() {27 emit_json(json.encode(RemoteJson{28 configured: false29 }))30 return 031 }32 ok("forgot this machine's remote")33 return 034 }35 if args.positional.len == 0 {36 if !machine.has_remote() {37 if args.json() {38 emit_json(json.encode(RemoteJson{39 configured: false40 }))41 return 042 }43 return setup_wizard()44 }45 settings := machine.load_remote() or { return refuse(args.json(), err.msg()) }46 if args.json() {47 emit_json(json.encode(RemoteJson{48 configured: true49 kind: settings.kind50 target: describe(settings)51 }))52 return 053 }54 ok('remote: ${describe(settings)}')55 note('run "koh remote folder <path>" or "koh remote s3 <endpoint> <bucket> <region>" to replace it')56 return 057 }58 59 return set_remote(args)60}6162fn set_remote(args Args) int {63 kind := args.positional_at(0)64 match kind {65 machine.kind_folder {66 path := args.positional_at(1)67 if path == '' {68 return usage_error('remote folder needs a path: koh remote folder /Volumes/usb/koh')69 }70 settings := machine.RemoteSettings{71 kind: machine.kind_folder72 path: os.real_path(path)73 }74 return save_after_check(settings, args, '', '')75 }76 machine.kind_s3 {77 endpoint := args.positional_at(1)78 bucket := args.positional_at(2)79 region := args.positional_at(3)80 if endpoint == '' || bucket == '' || region == '' {81 return usage_error('remote s3 needs an endpoint, bucket and region: koh remote s3 https://s3.fr-par.scw.cloud my-bucket fr-par')82 }83 access_key, secret_key := s3_keys() or { return refuse(args.json(), err.msg()) }84 settings := machine.RemoteSettings{85 kind: machine.kind_s386 endpoint: endpoint87 bucket: bucket88 region: region89 }90 return save_after_check(settings, args, access_key, secret_key)91 }92 else {93 return usage_error('remote takes "folder" or "s3", not "${kind}"')94 }95 }96}979899100fn save_after_check(settings machine.RemoteSettings, args Args, access_key string, secret_key string) int {101 mut backend := build_backend(settings, access_key, secret_key) or {102 return refuse(args.json(), err.msg())103 }104 remote.check(mut backend) or {105 return refuse(args.json(), 'the remote did not pass its test: ${err.msg()}')106 }107 machine.save_remote(settings) or { return refuse(args.json(), err.msg()) }108 if settings.kind == machine.kind_s3 {109 machine.save_credentials(machine.Credentials{110 access_key: access_key111 secret_key: secret_key112 }) or { return refuse(args.json(), err.msg()) }113 }114 if args.json() {115 emit_json(json.encode(RemoteJson{116 configured: true117 kind: settings.kind118 target: describe(settings)119 }))120 return 0121 }122 ok('remote set to ${describe(settings)}')123 note('every repo on this machine now offers to it')124 return 0125}126127fn build_backend(settings machine.RemoteSettings, access_key string, secret_key string) !remote.Backend {128 if settings.kind == machine.kind_folder {129 return remote.new_folder(settings.path)!130 }131 return remote.new_s3(settings.endpoint, settings.bucket, settings.region, access_key,132 secret_key)!133}134135fn setup_wizard() int {136 if !interactive() {137 return fail('setting up a remote needs a terminal, or use: koh remote folder <path>, koh remote s3 <endpoint> <bucket> <region>')138 }139 println(' choose a remote:')140 println(' ${fmt.amber('1')} folder a directory, on a disk or a NAS')141 println(' ${fmt.amber('2')} s3 an S3-compatible bucket')142 print(' which? [1] ')143 if os.get_line().trim_space() == '2' {144 return wizard_s3()145 }146 print(' folder path: ')147 path := os.get_line().trim_space()148 if path == '' {149 return fail('no path given — nothing was saved')150 }151 settings := machine.RemoteSettings{152 kind: machine.kind_folder153 path: os.real_path(path)154 }155 return save_after_check(settings, Args{}, '', '')156}157158fn wizard_s3() int {159 print(' endpoint [https://s3.fr-par.scw.cloud]: ')160 endpoint := or_default(os.get_line().trim_space(), 'https://s3.fr-par.scw.cloud')161 print(' bucket: ')162 bucket := os.get_line().trim_space()163 print(' region [fr-par]: ')164 region := or_default(os.get_line().trim_space(), 'fr-par')165 if bucket == '' {166 return fail('no bucket given — nothing was saved')167 }168 access_key, secret_key := s3_keys() or { return fail(err.msg()) }169 settings := machine.RemoteSettings{170 kind: machine.kind_s3171 endpoint: endpoint172 bucket: bucket173 region: region174 }175 return save_after_check(settings, Args{}, access_key, secret_key)176}177178179180fn s3_keys() !(string, string) {181 access_key := os.getenv('KOH_ACCESS_KEY')182 secret_key := os.getenv('KOH_SECRET_KEY')183 if access_key != '' && secret_key != '' {184 return access_key, secret_key185 }186 if !interactive() {187 return error('S3 keys are missing — set KOH_ACCESS_KEY and KOH_SECRET_KEY, or run koh remote on a terminal')188 }189 print(' access key: ')190 access_key_typed := os.get_line().trim_space()191 192 193 mut secret_key_typed := ''194 if os.is_atty(1) != 0 {195 secret_key_typed = os.input_password(' secret key: ') or {196 197 print(' secret key: ')198 os.get_line()199 }200 } else {201 print(' secret key: ')202 secret_key_typed = os.get_line()203 }204 secret_key_typed = secret_key_typed.trim_space()205 if access_key_typed == '' || secret_key_typed == '' {206 return error('both keys are needed — nothing was saved')207 }208 return access_key_typed, secret_key_typed209}210211fn describe(settings machine.RemoteSettings) string {212 if settings.kind == machine.kind_folder {213 return 'folder ${settings.path}'214 }215 return 's3 ${settings.endpoint}/${settings.bucket} (${settings.region})'216}217218fn or_default(value string, fallback string) string {219 return if value == '' { fallback } else { value }220}221222struct RemoteJson {223 configured bool224 kind string225 target string226}