1#!/usr/bin/env python3
2
3import argparse
4import os
5import subprocess
6import sys
7
8def setup():
9    global args, workdir
10    programs = ['ruby', 'git', 'apt-cacher-ng', 'make', 'wget']
11    if args.kvm:
12        programs += ['python-vm-builder', 'qemu-kvm', 'qemu-utils']
13    elif args.docker:
14        dockers = ['docker.io', 'docker-ce']
15        for i in dockers:
16            return_code = subprocess.call(['sudo', 'apt-get', 'install', '-qq', i])
17            if return_code == 0:
18                break
19        if return_code != 0:
20            print('Cannot find any way to install docker', file=sys.stderr)
21            exit(1)
22    else:
23        programs += ['lxc', 'debootstrap']
24    subprocess.check_call(['sudo', 'apt-get', 'install', '-qq'] + programs)
25    if not os.path.isdir('gitian.sigs.ltc'):
26        subprocess.check_call(['git', 'clone', 'https://github.com/litecoin-project/gitian.sigs.ltc.git'])
27    if not os.path.isdir('litecoin-detached-sigs'):
28        subprocess.check_call(['git', 'clone', 'https://github.com/litecoin-project/litecoin-detached-sigs.git'])
29    if not os.path.isdir('gitian-builder'):
30        subprocess.check_call(['git', 'clone', 'https://github.com/devrandom/gitian-builder.git'])
31    if not os.path.isdir('litecoin'):
32        subprocess.check_call(['git', 'clone', 'https://github.com/litecoin-project/litecoin.git'])
33    os.chdir('gitian-builder')
34    make_image_prog = ['bin/make-base-vm', '--suite', 'bionic', '--arch', 'amd64']
35    if args.docker:
36        make_image_prog += ['--docker']
37    elif not args.kvm:
38        make_image_prog += ['--lxc']
39    subprocess.check_call(make_image_prog)
40    os.chdir(workdir)
41    if args.is_bionic and not args.kvm and not args.docker:
42        subprocess.check_call(['sudo', 'sed', '-i', 's/lxcbr0/br0/', '/etc/default/lxc-net'])
43        print('Reboot is required')
44        exit(0)
45
46def build():
47    global args, workdir
48
49    os.makedirs('litecoin-binaries/' + args.version, exist_ok=True)
50    print('\nBuilding Dependencies\n')
51    os.chdir('gitian-builder')
52    os.makedirs('inputs', exist_ok=True)
53
54    subprocess.check_call(['wget', '-N', '-P', 'inputs', 'https://downloads.sourceforge.net/project/osslsigncode/osslsigncode/osslsigncode-1.7.1.tar.gz'])
55    subprocess.check_call(['wget', '-N', '-P', 'inputs', 'https://bitcoincore.org/cfields/osslsigncode-Backports-to-1.7.1.patch'])
56    subprocess.check_call(["echo 'a8c4e9cafba922f89de0df1f2152e7be286aba73f78505169bc351a7938dd911 inputs/osslsigncode-Backports-to-1.7.1.patch' | sha256sum -c"], shell=True)
57    subprocess.check_call(["echo 'f9a8cdb38b9c309326764ebc937cba1523a3a751a7ab05df3ecc99d18ae466c9 inputs/osslsigncode-1.7.1.tar.gz' | sha256sum -c"], shell=True)
58    subprocess.check_call(['make', '-C', '../litecoin/depends', 'download', 'SOURCES_PATH=' + os.getcwd() + '/cache/common'])
59
60    if args.linux:
61        print('\nCompiling ' + args.version + ' Linux')
62        subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'litecoin='+args.commit, '--url', 'litecoin='+args.url, '../litecoin/contrib/gitian-descriptors/gitian-linux.yml'])
63        subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-linux', '--destination', '../gitian.sigs.ltc/', '../litecoin/contrib/gitian-descriptors/gitian-linux.yml'])
64        subprocess.check_call('mv build/out/litecoin-*.tar.gz build/out/src/litecoin-*.tar.gz ../litecoin-binaries/'+args.version, shell=True)
65
66    if args.windows:
67        print('\nCompiling ' + args.version + ' Windows')
68        subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'litecoin='+args.commit, '--url', 'litecoin='+args.url, '../litecoin/contrib/gitian-descriptors/gitian-win.yml'])
69        subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-win-unsigned', '--destination', '../gitian.sigs.ltc/', '../litecoin/contrib/gitian-descriptors/gitian-win.yml'])
70        subprocess.check_call('mv build/out/litecoin-*-win-unsigned.tar.gz inputs/', shell=True)
71        subprocess.check_call('mv build/out/litecoin-*.zip build/out/litecoin-*.exe ../litecoin-binaries/'+args.version, shell=True)
72
73    if args.macos:
74        print('\nCompiling ' + args.version + ' MacOS')
75        subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'litecoin='+args.commit, '--url', 'litecoin='+args.url, '../litecoin/contrib/gitian-descriptors/gitian-osx.yml'])
76        subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-osx-unsigned', '--destination', '../gitian.sigs.ltc/', '../litecoin/contrib/gitian-descriptors/gitian-osx.yml'])
77        subprocess.check_call('mv build/out/litecoin-*-osx-unsigned.tar.gz inputs/', shell=True)
78        subprocess.check_call('mv build/out/litecoin-*.tar.gz build/out/litecoin-*.dmg ../litecoin-binaries/'+args.version, shell=True)
79
80    os.chdir(workdir)
81
82    if args.commit_files:
83        print('\nCommitting '+args.version+' Unsigned Sigs\n')
84        os.chdir('gitian.sigs.ltc')
85        subprocess.check_call(['git', 'add', args.version+'-linux/'+args.signer])
86        subprocess.check_call(['git', 'add', args.version+'-win-unsigned/'+args.signer])
87        subprocess.check_call(['git', 'add', args.version+'-osx-unsigned/'+args.signer])
88        subprocess.check_call(['git', 'commit', '-m', 'Add '+args.version+' unsigned sigs for '+args.signer])
89        os.chdir(workdir)
90
91def sign():
92    global args, workdir
93    os.chdir('gitian-builder')
94
95    if args.windows:
96        print('\nSigning ' + args.version + ' Windows')
97        subprocess.check_call('cp inputs/litecoin-' + args.version + '-win-unsigned.tar.gz inputs/litecoin-win-unsigned.tar.gz', shell=True)
98        subprocess.check_call(['bin/gbuild', '-i', '--commit', 'signature='+args.commit, '../litecoin/contrib/gitian-descriptors/gitian-win-signer.yml'])
99        subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-win-signed', '--destination', '../gitian.sigs.ltc/', '../litecoin/contrib/gitian-descriptors/gitian-win-signer.yml'])
100        subprocess.check_call('mv build/out/litecoin-*win64-setup.exe ../litecoin-binaries/'+args.version, shell=True)
101        subprocess.check_call('mv build/out/litecoin-*win32-setup.exe ../litecoin-binaries/'+args.version, shell=True)
102
103    if args.macos:
104        print('\nSigning ' + args.version + ' MacOS')
105        subprocess.check_call('cp inputs/litecoin-' + args.version + '-osx-unsigned.tar.gz inputs/litecoin-osx-unsigned.tar.gz', shell=True)
106        subprocess.check_call(['bin/gbuild', '-i', '--commit', 'signature='+args.commit, '../litecoin/contrib/gitian-descriptors/gitian-osx-signer.yml'])
107        subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-osx-signed', '--destination', '../gitian.sigs.ltc/', '../litecoin/contrib/gitian-descriptors/gitian-osx-signer.yml'])
108        subprocess.check_call('mv build/out/litecoin-osx-signed.dmg ../litecoin-binaries/'+args.version+'/litecoin-'+args.version+'-osx.dmg', shell=True)
109
110    os.chdir(workdir)
111
112    if args.commit_files:
113        print('\nCommitting '+args.version+' Signed Sigs\n')
114        os.chdir('gitian.sigs.ltc')
115        subprocess.check_call(['git', 'add', args.version+'-win-signed/'+args.signer])
116        subprocess.check_call(['git', 'add', args.version+'-osx-signed/'+args.signer])
117        subprocess.check_call(['git', 'commit', '-a', '-m', 'Add '+args.version+' signed binary sigs for '+args.signer])
118        os.chdir(workdir)
119
120def verify():
121    global args, workdir
122    os.chdir('gitian-builder')
123
124    print('\nVerifying v'+args.version+' Linux\n')
125    subprocess.check_call(['bin/gverify', '-v', '-d', '../gitian.sigs.ltc/', '-r', args.version+'-linux', '../litecoin/contrib/gitian-descriptors/gitian-linux.yml'])
126    print('\nVerifying v'+args.version+' Windows\n')
127    subprocess.check_call(['bin/gverify', '-v', '-d', '../gitian.sigs.ltc/', '-r', args.version+'-win-unsigned', '../litecoin/contrib/gitian-descriptors/gitian-win.yml'])
128    print('\nVerifying v'+args.version+' MacOS\n')
129    subprocess.check_call(['bin/gverify', '-v', '-d', '../gitian.sigs.ltc/', '-r', args.version+'-osx-unsigned', '../litecoin/contrib/gitian-descriptors/gitian-osx.yml'])
130    print('\nVerifying v'+args.version+' Signed Windows\n')
131    subprocess.check_call(['bin/gverify', '-v', '-d', '../gitian.sigs.ltc/', '-r', args.version+'-win-signed', '../litecoin/contrib/gitian-descriptors/gitian-win-signer.yml'])
132    print('\nVerifying v'+args.version+' Signed MacOS\n')
133    subprocess.check_call(['bin/gverify', '-v', '-d', '../gitian.sigs.ltc/', '-r', args.version+'-osx-signed', '../litecoin/contrib/gitian-descriptors/gitian-osx-signer.yml'])
134
135    os.chdir(workdir)
136
137def main():
138    global args, workdir
139
140    parser = argparse.ArgumentParser(usage='%(prog)s [options] signer version')
141    parser.add_argument('-c', '--commit', action='store_true', dest='commit', help='Indicate that the version argument is for a commit or branch')
142    parser.add_argument('-p', '--pull', action='store_true', dest='pull', help='Indicate that the version argument is the number of a github repository pull request')
143    parser.add_argument('-u', '--url', dest='url', default='https://github.com/litecoin-project/litecoin', help='Specify the URL of the repository. Default is %(default)s')
144    parser.add_argument('-v', '--verify', action='store_true', dest='verify', help='Verify the Gitian build')
145    parser.add_argument('-b', '--build', action='store_true', dest='build', help='Do a Gitian build')
146    parser.add_argument('-s', '--sign', action='store_true', dest='sign', help='Make signed binaries for Windows and MacOS')
147    parser.add_argument('-B', '--buildsign', action='store_true', dest='buildsign', help='Build both signed and unsigned binaries')
148    parser.add_argument('-o', '--os', dest='os', default='lwm', help='Specify which Operating Systems the build is for. Default is %(default)s. l for Linux, w for Windows, m for MacOS')
149    parser.add_argument('-j', '--jobs', dest='jobs', default='2', help='Number of processes to use. Default %(default)s')
150    parser.add_argument('-m', '--memory', dest='memory', default='2000', help='Memory to allocate in MiB. Default %(default)s')
151    parser.add_argument('-k', '--kvm', action='store_true', dest='kvm', help='Use KVM instead of LXC')
152    parser.add_argument('-d', '--docker', action='store_true', dest='docker', help='Use Docker instead of LXC')
153    parser.add_argument('-S', '--setup', action='store_true', dest='setup', help='Set up the Gitian building environment. Uses LXC. If you want to use KVM, use the --kvm option. Only works on Debian-based systems (Ubuntu, Debian)')
154    parser.add_argument('-D', '--detach-sign', action='store_true', dest='detach_sign', help='Create the assert file for detached signing. Will not commit anything.')
155    parser.add_argument('-n', '--no-commit', action='store_false', dest='commit_files', help='Do not commit anything to git')
156    parser.add_argument('signer', help='GPG signer to sign each build assert file')
157    parser.add_argument('version', help='Version number, commit, or branch to build. If building a commit or branch, the -c option must be specified')
158
159    args = parser.parse_args()
160    workdir = os.getcwd()
161
162    args.linux = 'l' in args.os
163    args.windows = 'w' in args.os
164    args.macos = 'm' in args.os
165
166    args.is_bionic = b'bionic' in subprocess.check_output(['lsb_release', '-cs'])
167
168    if args.buildsign:
169        args.build=True
170        args.sign=True
171
172    if args.kvm and args.docker:
173        raise Exception('Error: cannot have both kvm and docker')
174
175    args.sign_prog = 'true' if args.detach_sign else 'gpg --detach-sign'
176
177    # Set environment variable USE_LXC or USE_DOCKER, let gitian-builder know that we use lxc or docker
178    if args.docker:
179        os.environ['USE_DOCKER'] = '1'
180    elif not args.kvm:
181        os.environ['USE_LXC'] = '1'
182        if not 'GITIAN_HOST_IP' in os.environ.keys():
183            os.environ['GITIAN_HOST_IP'] = '10.0.3.1'
184        if not 'LXC_GUEST_IP' in os.environ.keys():
185            os.environ['LXC_GUEST_IP'] = '10.0.3.5'
186
187    # Disable for MacOS if no SDK found
188    if args.macos and not os.path.isfile('gitian-builder/inputs/MacOSX10.11.sdk.tar.gz'):
189        print('Cannot build for MacOS, SDK does not exist. Will build for other OSes')
190        args.macos = False
191
192    script_name = os.path.basename(sys.argv[0])
193    # Signer and version shouldn't be empty
194    if args.signer == '':
195        print(script_name+': Missing signer.')
196        print('Try '+script_name+' --help for more information')
197        exit(1)
198    if args.version == '':
199        print(script_name+': Missing version.')
200        print('Try '+script_name+' --help for more information')
201        exit(1)
202
203    # Add leading 'v' for tags
204    if args.commit and args.pull:
205        raise Exception('Cannot have both commit and pull')
206    args.commit = ('' if args.commit else 'v') + args.version
207
208    if args.setup:
209        setup()
210
211    os.chdir('litecoin')
212    if args.pull:
213        subprocess.check_call(['git', 'fetch', args.url, 'refs/pull/'+args.version+'/merge'])
214        os.chdir('../gitian-builder/inputs/litecoin')
215        subprocess.check_call(['git', 'fetch', args.url, 'refs/pull/'+args.version+'/merge'])
216        args.commit = subprocess.check_output(['git', 'show', '-s', '--format=%H', 'FETCH_HEAD'], universal_newlines=True, encoding='utf8').strip()
217        args.version = 'pull-' + args.version
218    print(args.commit)
219    subprocess.check_call(['git', 'fetch'])
220    subprocess.check_call(['git', 'checkout', args.commit])
221    os.chdir(workdir)
222
223    if args.build:
224        build()
225
226    if args.sign:
227        sign()
228
229    if args.verify:
230        verify()
231
232if __name__ == '__main__':
233    main()
234