From f4c7a98b6a0b9033635a935b3d181e192f369a9e Mon Sep 17 00:00:00 2001 From: pande Date: Tue, 23 Sep 2025 15:25:56 +0800 Subject: [PATCH] feat: add orchestrator scripts and Docker setup for droplet management; include SSH key handling and environment configuration --- .github/workflows/orchestratorci.yml | 16 ++-- orchestrator/Dockerfile | 10 +++ orchestrator/Makefile | 3 + orchestrator/cleanup.sh | 0 {actions => orchestrator}/orchestrating.sh | 77 ++++++++++++++++- runner/config.yml | 96 ++++++++++++++++++++++ runner/docker-compose.yml | 20 +++++ 7 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 orchestrator/Dockerfile create mode 100644 orchestrator/Makefile create mode 100644 orchestrator/cleanup.sh rename {actions => orchestrator}/orchestrating.sh (70%) mode change 100755 => 100644 create mode 100644 runner/config.yml create mode 100644 runner/docker-compose.yml diff --git a/.github/workflows/orchestratorci.yml b/.github/workflows/orchestratorci.yml index 1167beb..e1f3c41 100644 --- a/.github/workflows/orchestratorci.yml +++ b/.github/workflows/orchestratorci.yml @@ -13,11 +13,19 @@ on: secrets: DO_TOKEN: required: true + SSH_PRIVATE_KEY_DECODED: + required: true outputs: ready: description: "The result of the orchestrator job" value: ${{ jobs.provisioning.outputs.ready }} # Gunakan jobs + droplet_id: + description: "The ID of the created droplet" + value: ${{ jobs.provisioning.outputs.droplet_id }} + droplet_ip: + description: "The IP address of the created droplet" + value: ${{ jobs.provisioning.outputs.droplet_ip }} jobs: provisioning: @@ -28,13 +36,11 @@ jobs: steps: - name: Run OrchestratorCI id: provisioning - container: digitalocean/doctl + container: registry.btwazure.com/orchestrator/app:latest env: SPEC: ${{ inputs.spec }} RUNNER_ID: ${{ inputs.runner_id }} DO_TOKEN: ${{ secrets.DO_TOKEN }} + SSH_PRIVATE_KEY_DECODED: ${{ secrets.SSH_PRIVATE_KEY_DECODED }} run: | - echo "Preparing Script..." - # Placeholder for actual OrchestratorCI commands - # Simulate a result output - echo "ready=true" >> $GITHUB_OUTPUT \ No newline at end of file + ./orchestrating.sh \ No newline at end of file diff --git a/orchestrator/Dockerfile b/orchestrator/Dockerfile new file mode 100644 index 0000000..e380e83 --- /dev/null +++ b/orchestrator/Dockerfile @@ -0,0 +1,10 @@ +FROM alpine:latest +RUN apk --no-cache add curl bash git doctl openssh + +WORKDIR /root + +COPY orchestrating.sh orchestrating.sh +COPY cleanup.sh cleanup.sh +RUN chmod +x orchestrating.sh cleanup.sh + +CMD ["tail" ,"-f" ,"/dev/null"] \ No newline at end of file diff --git a/orchestrator/Makefile b/orchestrator/Makefile new file mode 100644 index 0000000..b905496 --- /dev/null +++ b/orchestrator/Makefile @@ -0,0 +1,3 @@ +build: + podman build -t registry.btwazure.com/orchestrator/app:latest . + podman push registry.btwazure.com/orchestrator/app:latest \ No newline at end of file diff --git a/orchestrator/cleanup.sh b/orchestrator/cleanup.sh new file mode 100644 index 0000000..e69de29 diff --git a/actions/orchestrating.sh b/orchestrator/orchestrating.sh old mode 100755 new mode 100644 similarity index 70% rename from actions/orchestrating.sh rename to orchestrator/orchestrating.sh index edfd00e..3733e72 --- a/actions/orchestrating.sh +++ b/orchestrator/orchestrating.sh @@ -50,7 +50,7 @@ setup_ssh_key() { chmod 600 ~/.ssh/id_rsa - # Validate SSH key format + # Validate SSH key formats if ! ssh-keygen -y -f ~/.ssh/id_rsa > /dev/null 2>&1; then echo "Error: Invalid SSH private key format" echo "Key content preview (first 100 chars):" @@ -92,7 +92,7 @@ destroy_droplet() { create_droplet() { local name="${1:-runner-${RUNNER_ID}}" local size="${2:-s-1vcpu-1gb}" - local image="${3:-ubuntu-24-04-x64}" + local image="${3:-200811356}" local region="${4:-sgp1}" echo "Creating droplet: $name" @@ -115,13 +115,26 @@ create_droplet() { echo "Droplet created successfully with ID: $droplet_id" echo "Droplet name: $name" + echo "droplet_id=$droplet_id" >> $GITHUB_OUTPUT + # Get droplet IP local droplet_ip=$(doctl compute droplet get "$droplet_id" --format PublicIPv4 --no-header) echo "Droplet IP: $droplet_ip" + echo "droplet_ip=$droplet_ip" >> $GITHUB_OUTPUT # Test SSH connection if test_ssh_connection "$droplet_ip"; then echo "Droplet is ready and accessible via SSH" + + # Setup VM with required services + if setup_vm "$droplet_ip"; then + echo "VM setup completed successfully" + else + echo "Error: VM setup failed" + echo "Destroying failed droplet..." + destroy_droplet "$droplet_id" + return 1 + fi else echo "Warning: Droplet created but SSH connection failed" echo "Destroying failed droplet..." @@ -179,6 +192,63 @@ test_ssh_connection() { return 1 } +# Function to setup VM after successful SSH connection +setup_vm() { + local droplet_ip="$1" + + if [ -z "$droplet_ip" ]; then + echo "Error: No droplet IP provided for VM setup" + return 1 + fi + + echo "Setting up VM at $droplet_ip..." + + # Execute setup commands via SSH + echo "Step 1: Running prepare.sh config.yml..." + if ! ssh -i ~/.ssh/id_rsa \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ConnectTimeout=30 \ + root@"$droplet_ip" \ + "./prepare.sh config.yml RUNNER_ID $RUNNER_ID"; then + echo "Error: Failed to run prepare.sh config.yml" + return 1 + fi + + echo "Step 2: Running prepare.sh docker-compose.yml..." + if ! ssh -i ~/.ssh/id_rsa \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ConnectTimeout=30 \ + root@"$droplet_ip" \ + "./prepare.sh docker-compose.yml RUNNER_ID $RUNNER_ID"; then + echo "Error: Failed to run prepare.sh docker-compose.yml" + return 1 + fi + + echo "Step 3: Starting docker compose services..." + if ! ssh -i ~/.ssh/id_rsa \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ConnectTimeout=30 \ + root@"$droplet_ip" \ + "docker compose up -d"; then + echo "Error: Failed to start docker compose services" + return 1 + fi + + echo "Step 4: Checking docker compose status..." + ssh -i ~/.ssh/id_rsa \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ConnectTimeout=30 \ + root@"$droplet_ip" \ + "docker compose ps" + + echo "VM setup completed successfully" + return 0 +} + # Check required environment variables check_env_vars "SPEC" "RUNNER_ID" "DO_TOKEN" "SSH_PRIVATE_KEY_DECODED" @@ -193,4 +263,5 @@ create_droplet # Calculate and display execution time END_TIME=$(date +%s) EXECUTION_TIME=$((END_TIME - START_TIME)) -echo "Script execution time: ${EXECUTION_TIME} seconds" \ No newline at end of file +echo "Script execution time: ${EXECUTION_TIME} seconds" +echo "ready=true" >> $GITHUB_OUTPUT \ No newline at end of file diff --git a/runner/config.yml b/runner/config.yml new file mode 100644 index 0000000..ebb5c2d --- /dev/null +++ b/runner/config.yml @@ -0,0 +1,96 @@ +# Example configuration file, it's safe to copy this as the default config file without any modification. + +# You don't have to copy this file to your instance, +# just run `./act_runner generate-config > config.yaml` to generate a config file. + +log: + # The level of logging, can be trace, debug, info, warn, error, fatal + level: info + +runner: + # Where to store the registration result. + file: .runner + # Execute how many tasks concurrently at the same time. + capacity: 1 + # Extra environment variables to run jobs. + envs: + A_TEST_ENV_NAME_1: a_test_env_value_1 + A_TEST_ENV_NAME_2: a_test_env_value_2 + # Extra environment variables to run jobs from a file. + # It will be ignored if it's empty or the file doesn't exist. + env_file: .env + # The timeout for a job to be finished. + # Please note that the Gitea instance also has a timeout (3h by default) for the job. + # So the job could be stopped by the Gitea instance if it's timeout is shorter than this. + timeout: 3h + # Whether skip verifying the TLS certificate of the Gitea instance. + insecure: false + # The timeout for fetching the job from the Gitea instance. + fetch_timeout: 5s + # The interval for fetching the job from the Gitea instance. + fetch_interval: 2s + # The labels of a runner are used to determine which jobs the runner can run, and how to run them. + # Like: "macos-arm64:host" or "ubuntu-latest:docker://catthehacker/ubuntu:runner-latest" + # Find more images provided by Gitea at https://gitea.com/gitea/runner-images . + # If it's empty when registering, it will ask for inputting labels. + # If it's empty when execute `daemon`, will use labels in `.runner` file. + labels: + - "${RUNNER_ID}:docker://catthehacker/ubuntu:runner-22.04" + +cache: + # Enable cache server to use actions/cache. + enabled: true + # The directory to store the cache data. + # If it's empty, the cache data will be stored in $HOME/.cache/actcache. + dir: "" + # The host of the cache server. + # It's not for the address to listen, but the address to connect from job containers. + # So 0.0.0.0 is a bad choice, leave it empty to detect automatically. + host: "" + # The port of the cache server. + # 0 means to use a random available port. + port: 0 + # The external cache server URL. Valid only when enable is true. + # If it's specified, act_runner will use this URL as the ACTIONS_CACHE_URL rather than start a server by itself. + # The URL should generally end with "/". + external_server: "" + +container: + # Specifies the network to which the container will connect. + # Could be host, bridge or the name of a custom network. + # If it's empty, act_runner will create a network automatically. + network: "" + # Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker). + privileged: false + # And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway). + options: + # The parent directory of a job's working directory. + # NOTE: There is no need to add the first '/' of the path as act_runner will add it automatically. + # If the path starts with '/', the '/' will be trimmed. + # For example, if the parent directory is /path/to/my/dir, workdir_parent should be path/to/my/dir + # If it's empty, /workspace will be used. + workdir_parent: + # Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob + # You can specify multiple volumes. If the sequence is empty, no volumes can be mounted. + # For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to: + # valid_volumes: + # - data + # - /src/*.json + # If you want to allow any volume, please use the following configuration: + # valid_volumes: + # - '**' + valid_volumes: [] + # overrides the docker client host with the specified one. + # If it's empty, act_runner will find an available docker host automatically. + # If it's "-", act_runner will find an available docker host automatically, but the docker host won't be mounted to the job containers and service containers. + # If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work. + docker_host: "" + # Pull docker image(s) even if already present + force_pull: true + # Rebuild docker image(s) even if already present + force_rebuild: false + +host: + # The parent directory of a job's working directory. + # If it's empty, $HOME/.cache/act/ will be used. + workdir_parent: \ No newline at end of file diff --git a/runner/docker-compose.yml b/runner/docker-compose.yml new file mode 100644 index 0000000..3ddf9ed --- /dev/null +++ b/runner/docker-compose.yml @@ -0,0 +1,20 @@ +services: + runner: + image: vegardit/gitea-act-runner:dind-latest + container_name: runner + privileged: true + restart: always + user: act + environment: + CONFIG_FILE: /config.yaml + GITEA_INSTANCE_URL: https://git.btwazure.com + GITEA_RUNNER_REGISTRATION_TOKEN: oks0bYdhBLfuxhxAMpoWbYt0JyVfKATFsBz41lKo + GITEA_RUNNER_NAME: runner-${RUNNER_ID} + GITEA_RUNNER_LABELS: "${RUNNER_ID}:docker://catthehacker/ubuntu:js-22.04,ubuntu-js-latest:docker://catthehacker/ubuntu:js-22.04" + # GITEA_RUNNER_LABELS: "gitea-orchestrator:docker://catthehacker/ubuntu:runner-22.04" + TZ: Asia/Jakarta + volumes: + - ./config.yaml:/config.yaml + - ./data:/data + stdin_open: true + tty: true \ No newline at end of file