Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Continuous Deployment #544

Merged
merged 2 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/actions/install-cert/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Install Certificate in Keychain
description: Install a single cert in existing keychain

inputs:
KEYCHAIN:
required: true
KEYCHAIN_PASSWORD:
required: true
SIGNING_CERTIFICATE:
required: true
SIGNING_CERTIFICATE_P12_PASSWORD:
required: true

runs:
using: composite
steps:
- name: Install certificate
shell: bash
env:
KEYCHAIN: ${{ inputs.KEYCHAIN }}
KEYCHAIN_PASSWORD: ${{ inputs.KEYCHAIN_PASSWORD }}
CERTIFICATE_PATH: /tmp/cert.p12
SIGNING_CERTIFICATE: ${{ inputs.SIGNING_CERTIFICATE }}
SIGNING_CERTIFICATE_P12_PASSWORD: ${{ inputs.SIGNING_CERTIFICATE_P12_PASSWORD }}
run: |
security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN
echo "${SIGNING_CERTIFICATE}" | base64 --decode -o $CERTIFICATE_PATH
security import $CERTIFICATE_PATH -k $KEYCHAIN -P "${SIGNING_CERTIFICATE_P12_PASSWORD}" -A -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
rm $CERTIFICATE_PATH
security find-identity -v $KEYCHAIN
security set-key-partition-list -S apple-tool:,apple: -s -k $KEYCHAIN_PASSWORD $KEYCHAIN
133 changes: 133 additions & 0 deletions .github/actions/xcbuild/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
name: Build with XCode
description: Run xcodebuild for Kiwix

inputs:
action:
required: true
version:
required: true
xc-destination:
required: true
upload-to:
required: true
libkiwix-version:
required: true
APPLE_DEVELOPMENT_SIGNING_CERTIFICATE:
required: true
APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD:
required: true
DEPLOYMENT_SIGNING_CERTIFICATE:
required: false
DEPLOYMENT_SIGNING_CERTIFICATE_P12_PASSWORD:
required: false
KEYCHAIN:
required: false
default: /Users/runner/build.keychain-db
KEYCHAIN_PASSWORD:
required: false
default: mysecretpassword
KEYCHAIN_PROFILE:
required: false
default: build-profile
XC_WORKSPACE:
required: false
default: Kiwix.xcodeproj/project.xcworkspace/
XC_SCHEME:
required: false
default: Kiwix
XC_CONFIG:
required: false
default: Release
EXTRA_XCODEBUILD:
required: false
default: ""

runs:
using: composite
steps:

# not necessary on github runner but serves as documentation for local setup
- name: Update Apple Intermediate Certificate
shell: bash
run: |
curl -L -o ~/Downloads/AppleWWDRCAG3.cer https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer
sudo security import ~/Downloads/AppleWWDRCAG3.cer \
-k /Library/Keychains/System.keychain \
-T /usr/bin/codesign \
-T /usr/bin/security \
-T /usr/bin/productbuild || true

- name: Set Xcode version (15.0.1)
shell: bash
# https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md#xcode
run: sudo xcode-select -s /Applications/Xcode_15.0.1.app

- name: Create Keychain
shell: bash
env:
KEYCHAIN: ${{ inputs.KEYCHAIN }}
KEYCHAIN_PASSWORD: ${{ inputs.KEYCHAIN_PASSWORD }}
KEYCHAIN_PROFILE: ${{ inputs.KEYCHAIN_PROFILE }}
CERTIFICATE_PATH: /tmp/cert.p12
APPLE_DEVELOPER_CERTIFICATE_PATH: /tmp/dev-cert.p12
SIGNING_CERTIFICATE: ${{ inputs.SIGNING_CERTIFICATE }}
SIGNING_CERTIFICATE_P12_PASSWORD: ${{ inputs.SIGNING_CERTIFICATE_P12_PASSWORD }}
APPLE_DEVELOPER_ID_SIGNING_CERTIFICATE: ${{ inputs.APPLE_DEVELOPER_ID_SIGNING_CERTIFICATE }}
APPLE_DEVELOPER_ID_SIGNING_P12_PASSWORD: ${{ inputs.APPLE_DEVELOPER_ID_SIGNING_P12_PASSWORD }}
run: |
security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN
security default-keychain -s $KEYCHAIN
security set-keychain-settings $KEYCHAIN
security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN

- name: Add Apple Development certificate to Keychain
uses: ./.github/actions/install-cert
with:
SIGNING_CERTIFICATE: ${{ inputs.APPLE_DEVELOPMENT_SIGNING_CERTIFICATE }}
SIGNING_CERTIFICATE_P12_PASSWORD: ${{ inputs.APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD }}
KEYCHAIN: ${{ inputs.KEYCHAIN }}
KEYCHAIN_PASSWORD: ${{ inputs.KEYCHAIN_PASSWORD }}

- name: Add Distribution certificate to Keychain
if: ${{ inputs.DEPLOYMENT_SIGNING_CERTIFICATE }}
uses: ./.github/actions/install-cert
with:
SIGNING_CERTIFICATE: ${{ inputs.DEPLOYMENT_SIGNING_CERTIFICATE }}
SIGNING_CERTIFICATE_P12_PASSWORD: ${{ inputs.DEPLOYMENT_SIGNING_CERTIFICATE_P12_PASSWORD }}
KEYCHAIN: ${{ inputs.KEYCHAIN }}
KEYCHAIN_PASSWORD: ${{ inputs.KEYCHAIN_PASSWORD }}

- name: Download CoreKiwix.xcframework
env:
XCF_URL: https://download.kiwix.org/release/libkiwix/libkiwix_xcframework-${{ inputs.libkiwix-version }}.tar.gz
shell: bash
run: curl -L -o - $XCF_URL | tar -x --strip-components 2

- name: Prepare Xcode
shell: bash
run: xcrun xcodebuild -checkFirstLaunchStatus || xcrun xcodebuild -runFirstLaunch

- name: Dump build settings
env:
XC_WORKSPACE: ${{ inputs.XC_WORKSPACE }}
XC_SCHEME: ${{ inputs.XC_SCHEME }}
shell: bash
run: xcrun xcodebuild -workspace $XC_WORKSPACE -scheme $XC_SCHEME -showBuildSettings
# build is launched up to twice as it's common the build fails, looking for CoreKiwix module

- name: Install retry command
shell: bash
run: brew install kadwanev/brew/retry

- name: Build with Xcode
env:
FRAMEWORK_SEARCH_PATHS: ${{ env.PWD }}
ACTION: ${{ inputs.action }}
VERSION: ${{ inputs.version }}
XC_WORKSPACE: ${{ inputs.XC_WORKSPACE }}
XC_SCHEME: ${{ inputs.XC_SCHEME }}
XC_CONFIG: ${{ inputs.XC_CONFIG }}
XC_DESTINATION: ${{ inputs.xc-destination }}
EXTRA_XCODEBUILD: ${{ inputs.EXTRA_XCODEBUILD }}
shell: bash
run: retry -t 2 -- xcrun xcodebuild ${EXTRA_XCODEBUILD} -workspace $XC_WORKSPACE -scheme $XC_SCHEME -destination "$XC_DESTINATION" -configuration $XC_CONFIG -onlyUsePackageVersionsFromResolvedFile -allowProvisioningUpdates -verbose -archivePath $PWD/Kiwix-$VERSION.xcarchive ${ACTION}
Binary file added .github/dmg-bg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions .github/dmg-settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from pathlib import Path

application = defines.get("app", "Kiwix.app") # noqa: F821
background = defines.get("bg", "bg.png") # noqa: F821
appname = Path(application).name
# Volume format (see hdiutil create -help)
format = defines.get("format", "ULMO") # noqa: F821
# Compression level (if relevant)
# compression_level = 9
# Volume size
size = defines.get("size", None) # noqa: F821
# Files to include
files = [application]
# Symlinks to create
symlinks = {"Applications": "/Applications"}
# Files to hide the extension of
hide_extension = [ "Kiwix.app" ]
# Volume icon (reuse from app)
icon = Path(application).joinpath("Contents/Resources/AppIcon.icns")
# Where to put the icons
icon_locations = {appname: (146, 180), "Applications": (481, 181)}

background = background
show_status_bar = False
show_tab_view = False
show_toolbar = False
show_pathbar = False
show_sidebar = False
sidebar_width = 180

# Window position in ((x, y), (w, h)) format
window_rect = ((200, 120), (600, 360))
default_view = "icon-view"
show_icon_preview = True
# Set these to True to force inclusion of icon/list view settings (otherwise
# we only include settings for the default view)
include_icon_view_settings = True
include_list_view_settings = True
# .. Icon view configuration ...................................................
arrange_by = None
grid_offset = (0, 0)
grid_spacing = 100
scroll_position = (0, 0)
label_pos = "bottom" # or 'right'
text_size = 16
icon_size = 100
75 changes: 75 additions & 0 deletions .github/retry-if-retcode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env python3

import argparse
import subprocess
import sys
import time


def run_command(
max_attempts: int, retcode: int, sleep_seconds: int, command: str
) -> int:
attempts = 0
while True:
ps = subprocess.run(command, check=False)
attempts += 1

# either suceeded or returned an unexpected exit-code, returning.
if ps.returncode == 0 or ps.returncode != retcode:
return ps.returncode

if attempts >= max_attempts:
print(f"Reached {max_attempts=}")
return ps.returncode

print(
f"Received retcode={ps.returncode} on attempt #{attempts}. "
f"Retrying in {sleep_seconds}s."
)
if sleep_seconds:
time.sleep(sleep_seconds)


def main():
parser = argparse.ArgumentParser(
prog="retry-if-retcode", epilog=r"/!\ Append your command after those args!"
)

parser.add_argument(
"--retcode",
required=True,
help="Return code to retry when received",
type=int,
)

parser.add_argument(
"--attempts",
required=False,
help="Max number of attempts",
type=int,
default=10,
)

parser.add_argument(
"--sleep",
required=False,
help="Nb. of seconds to sleep in-between retries",
type=int,
default=1,
)

args, command = parser.parse_known_args()
if not command:
print("You must supply a command to run")
return 1

return run_command(
max_attempts=args.attempts,
retcode=args.retcode,
sleep_seconds=args.sleep,
command=command,
)


if __name__ == "__main__":
sys.exit(main())
110 changes: 110 additions & 0 deletions .github/upload_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import argparse
import os
import pathlib
import subprocess
import sys
import urllib.parse


def main() -> int:
parser = argparse.ArgumentParser(
prog="scp-upload",
description="Upload files to Kiwix server",
)

parser.add_argument(
"--src", required=True, help="filepath to be uploaded", dest="src_path"
)

parser.add_argument(
"--dest",
required=True,
help="destination as user@host[:port]/folder/",
dest="dest",
)

parser.add_argument(
"--ssh-key",
required=False,
help="filepath to the private key to use for upload",
default=os.getenv("SSH_KEY", ""),
dest="ssh_key",
)

args = parser.parse_args()

ssh_path = (
pathlib.Path(args.ssh_key or os.getenv("SSH_KEY", "")).expanduser().resolve()
)
src_path = pathlib.Path(args.src_path).expanduser().resolve()
dest = urllib.parse.urlparse(f"ssh://{args.dest}")
dest_path = pathlib.Path(dest.path)

if not src_path.exists() or not ssh_path.is_file():
print(f"Source file “{src_path}” missing")
return 1

if not ssh_path.exists() or not ssh_path.is_file():
print(f"SSH Key “{ssh_path}” missing")
return 1

if not dest_path or dest_path == pathlib.Path("") or dest_path == pathlib.Path("/"):
print(f"Must upload in a subfoler, not “{dest_path}”")
return 1

return upload(
src_path=src_path, host=dest.netloc, dest_path=dest_path, ssh_path=ssh_path
)


def upload(
src_path: pathlib.Path, host: str, dest_path: pathlib.Path, ssh_path: pathlib.Path
) -> int:
if ":" in host:
host, port = host.split(":", 1)
else:
port = "22"

# sending SFTP mkdir command to the sftp interactive mode and not batch (-b) mode
# as the latter would exit on any mkdir error while it is most likely
# the first parts of the destination is already present and thus can't be created
sftp_commands = "\n".join(
[
f"mkdir {part}"
for part in list(reversed(dest_path.parents)) + [str(dest_path)]
]
)
command = [
"sftp",
"-i",
str(ssh_path),
"-P",
port,
"-o",
"StrictHostKeyChecking=no",
host,
]
print(f"Creating dest path: {dest_path}")
subprocess.run(command, input=sftp_commands, text=True, check=True)

command = [
"scp",
"-c",
"aes128-ctr",
"-rp",
"-P",
port,
"-i",
str(ssh_path),
"-o",
"StrictHostKeyChecking=no",
str(src_path),
f"{host}:{dest_path}/",
]
print(f"Sending archive with command {' '.join(command)}")
subprocess.run(command, check=True)
return 0


if __name__ == "__main__":
sys.exit(main())
Loading
Loading