{"id":20509026,"url":"https://github.com/shinesolutions/ruby_aem","last_synced_at":"2025-08-17T16:37:44.216Z","repository":{"id":55995273,"uuid":"62266641","full_name":"shinesolutions/ruby_aem","owner":"shinesolutions","description":"Ruby client for Adobe Experience Manager (AEM) API","archived":false,"fork":false,"pushed_at":"2024-07-29T13:47:33.000Z","size":1638,"stargazers_count":13,"open_issues_count":2,"forks_count":7,"subscribers_count":15,"default_branch":"main","last_synced_at":"2025-06-28T18:45:29.395Z","etag":null,"topics":["aem","aem-opencloud","api-client","ruby"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shinesolutions.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-06-30T00:27:08.000Z","updated_at":"2024-07-29T13:47:32.000Z","dependencies_parsed_at":"2024-07-29T15:07:30.580Z","dependency_job_id":null,"html_url":"https://github.com/shinesolutions/ruby_aem","commit_stats":null,"previous_names":[],"tags_count":58,"template":false,"template_full_name":null,"purl":"pkg:github/shinesolutions/ruby_aem","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shinesolutions%2Fruby_aem","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shinesolutions%2Fruby_aem/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shinesolutions%2Fruby_aem/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shinesolutions%2Fruby_aem/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shinesolutions","download_url":"https://codeload.github.com/shinesolutions/ruby_aem/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shinesolutions%2Fruby_aem/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266081235,"owners_count":23873508,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aem","aem-opencloud","api-client","ruby"],"created_at":"2024-11-15T20:21:53.017Z","updated_at":"2025-07-20T07:02:27.822Z","avatar_url":"https://github.com/shinesolutions.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://github.com/shinesolutions/ruby_aem/workflows/CI/badge.svg)](https://github.com/shinesolutions/ruby_aem/actions?query=workflow%3ACI)\n[![Published Version](https://badge.fury.io/rb/ruby_aem.svg)](https://rubygems.org/gems/ruby_aem)\n[![Known Vulnerabilities](https://snyk.io/test/github/shinesolutions/ruby_aem/badge.svg)](https://snyk.io/test/github/shinesolutions/ruby_aem)\n\nruby_aem\n--------\n\nruby_aem is a Ruby client for [Adobe Experience Manager (AEM)](http://www.adobe.com/au/marketing-cloud/enterprise-content-management.html) API.\nIt is written on top of [swagger_aem](https://github.com/shinesolutions/swagger-aem/blob/master/ruby/README.md) and provides resource-oriented API and convenient response handling.\n\nLearn more about ruby_aem:\n\n* [Installation](https://github.com/shinesolutions/ruby_aem#installation)\n* [Usage](https://github.com/shinesolutions/ruby_aem#usage)\n* [Result Model](https://github.com/shinesolutions/ruby_aem#result)\n* [Error Handling](https://github.com/shinesolutions/ruby_aem#error-handling)\n* [Testing](https://github.com/shinesolutions/ruby_aem#testing)\n* [Versions History](https://github.com/shinesolutions/ruby_aem/blob/master/docs/versions.md)\n\nruby_aem is part of [AEM OpenCloud](https://aemopencloud.io) platform but it can be used as a stand-alone.\n\nInstallation\n------------\n\n    gem install ruby_aem\n\nUsage\n-----\n\nInitialise client:\n\n    require 'ruby_aem'\n\n    aem = RubyAem::Aem.new({\n      username: 'admin',\n      password: 'admin',\n      protocol: 'http',\n      host: 'localhost',\n      port: 4502,\n      timeout: 300,\n      verify_ssl: true,\n      debug: false\n    })\n\nAem:\n\n    # wait until AEM login page is ready\n    aem = aem.aem\n    result = aem.get_login_page_wait_until_ready({\n      _retries: {\n        max_tries: 60,\n        base_sleep_seconds: 2,\n        max_sleep_seconds: 2\n      }})\n\n    # wait until AEM Health Check has OK status\n    # this requires aem-healthcheck package to be installed\n    # https://github.com/shinesolutions/aem-healthcheck\n    aem = aem.aem\n    result = aem.get_aem_health_check_wait_until_ok({\n      tags: 'shallow',\n      combine_tags_or: false,\n      _retries: {\n        max_tries: 60,\n        base_sleep_seconds: 2,\n        max_sleep_seconds: 2\n      }})\n\n    # get an array of all agent names within AEM author or publish instance\n    aem = aem.aem\n    result = aem.get_agents('author')\n\n    # get an array of AEM product informations\n    aem = aem.aem\n    result = aem.get_product_info\n\nAEM Config Manager:\n\n    # Create OpenAPI Spec of all configuration nodes\n    configmgr = aem.aem_configmgr('./source_api.yaml', 'api_dest.yaml')\n    result = configmgr.get_all_configuration_nodes\n\nBundle:\n\n    # stop bundle\n    bundle = aem.bundle('com.adobe.cq.social.cq-social-forum')\n    result = bundle.stop\n\n    # start bundle\n    bundle = aem.bundle('com.adobe.cq.social.cq-social-forum')\n    result = bundle.start\n\nConfiguration property:\n\n    config_property = aem.config_property('someproperty', 'Boolean', true)\n\n    # set config property on /apps/system/config/somenode\n    result = config_property.create('somenode')\n\nFlush agent:\n\n    flush_agent = aem.flush_agent('author', 'some-flush-agent')\n\n    # create or update flush agent\n    opts = { log_level: 'info', retry_delay: 60_000 }\n    result = flush_agent.create_update('Some Flush Agent Title', 'Some flush agent description', 'http://somehost:8080', opts)\n\n    # check flush agent's existence\n    result = flush_agent.exists\n\n    # delete flush agent\n    result = flush_agent.delete\n\nGroup:\n\n    # create group\n    group = aem.group('/home/groups/s/', 'somegroup')\n\n    # check group's existence\n    result = group.exists\n\n    # set group permission\n    result = group.set_permission('/etc/replication', 'read:true,modify:true')\n\n    # add another group as a member\n    member_group = aem.group('/home/groups/s/', 'somemembergroup')\n    result = member_group.create\n    result = group.add_member('somemembergroup')\n\n    # delete group\n    result = group.delete\n\nNode:\n\n    node = aem.node('/apps/system/', 'somefolder')\n\n    # create node\n    result = node.create('sling:Folder')\n\n    # check node's existence\n    result = node.exists\n\n    # delete node\n    result = node.delete\n\nPackage:\n\n    package = aem.package('somepackagegroup', 'somepackage', '1.2.3')\n\n    # upload package located at /tmp/somepackage-1.2.3.zip\n    opts = { force: true }\n    result = package.upload('/tmp', opts)\n\n    # check whether package is uploaded\n    result = package.is_uploaded\n\n    # install package\n    opts = { recursive: true }\n    result = package.install(opts)\n\n    # uninstall package\n    result = package.uninstall(opts)\n\n    # check whether package is installed\n    result = package.is_installed\n\n    # replicate package\n    result = package.replicate\n\n    # download package to /tmp directory\n    result = package.download('/tmp')\n\n    # create package\n    result = package.create\n\n    # build package\n    result = package.build\n\n    # build package and wait until package is built (package exists and size is not empty)\n    result = package.build_wait_until_ready\n\n    # check whether package is built\n    result = package.is_built\n\n    # update package filter\n    result = package.update('[{\"root\":\"/apps/geometrixx\",\"rules\":[]},{\"root\":\"/apps/geometrixx-common\",\"rules\":[]}]')\n\n    # get package filter\n    result = package.get_filter\n\n    # activate filter\n    results = package.activate_filter(true, false)\n\n    # list all packages\n    result = package.list_all\n\n    # check whether package is empty\n    result = package.is_empty\n\n    # get all versions of the package\n    result = package.get_versions\n\nPath:\n\n    # check path's existence\n    path = aem.path('/etc/designs/cloudservices')\n    result = path.activate(true, false)\n\n    # tree activate the path\n    path = aem.path('/etc/designs')\n    result = path.activate(true, false)\n\nReplication agent:\n\n    replication_agent = aem.replication_agent('author', 'some-replication-agent')\n\n    # create or update replication agent\n    opts = {\n      transport_user: 'admin',\n      transport_password: 'admin',\n      log_level: 'info',\n      retry_delay: 60_000,\n      ssl: 'relaxed'\n    }\n    result = replication_agent.create_update('Some Replication Agent Title', 'Some replication agent description', 'http://somehost:8080', opts)\n\n    # check replication agent's existence\n    result = replication_agent.exists\n\n    # delete replication agent\n    result = replication_agent.delete\n\nOutbox replication agent:\n\n    outbox_replication_agent = aem.outbox_replication_agent('publish', 'some-outbox-replication-agent')\n\n    # create or update outbox replication agent\n    opts = {\n      user_id: 'admin',\n      log_level: 'info'\n    }\n    result = outbox_replication_agent.create_update('Some Outbox Replication Agent Title', 'Some outbox replication agent description', 'http://somehost:8080', opts)\n\n    # check outbox replication agent's existence\n    result = outbox_replication_agent.exists\n\n    # delete outbox replication agent\n    result = outbox_replication_agent.delete\n\nReverse replication agent:\n\n    reverse_replication_agent = aem.reverse_replication_agent('author', 'some-reverse-replication-agent')\n\n    # create or update reverse replication agent\n    opts = {\n      transport_user: 'admin',\n      transport_password: 'admin',\n      log_level: 'info',\n      retry_delay: 60_000\n    }\n    result = reverse_replication_agent.create_update('Some Reverse Replication Agent Title', 'Some reverse replication agent description', 'http://somehost:8080', opts)\n\n    # check reverse replication agent's existence\n    result = reverse_replication_agent.exists\n\n    # delete reverse replication agent\n    result = reverse_replication_agent.delete\n\nRepository:\n\n    repository = aem.repository\n\n    # block repository writes\n    result = repository.block_writes\n\n    # unblock repository writes\n    result = repository.unblock_writes\n\nSaml:\n\n    saml = aem.saml\n\n    # Configure SAML for AEM\n    opts = {\n      key_store_password: 'someKeystorePassword',\n      service_ranking: 5002,\n      idp_http_redirect: true,\n      create_user: true,\n      default_redirect_url: '/some_sites.html',\n      user_id_attribute: 'someUserID',\n      default_groups: ['some-groups'],\n      idp_cert_alias: 'some_alias_name_1234'.\n      add_group_memberships: true,\n      path: ['/'],\n      synchronize_attributes: [\n      'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname\\=profile/givenName',\n      'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname\\=profile/familyName',\n      'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\\=profile/email'\n      ],\n      clock_tolerance: 60,\n      group_membership_attribute: 'http://temp/variable/aem-groups',\n      idp_url: 'https://federation.prod.com/adfs/ls/IdpInitiatedSignOn.aspx?RequestBinding\\=HTTPPost\u0026loginToRp\\=https://prod-aemauthor.com/saml_login',\n      logout_url: 'https://federation.prod.com/adfs/ls/IdpInitiatedSignOn.aspx',\n      service_provider_entity_id: 'https://prod-aemauthor.com/saml_login',\n      handle_logout: true,\n      sp_private_key_alias: '',\n      use_encryption: false,\n      name_id_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient',\n      digest_method\t: 'http://www.w3.org/2001/04/xmlenc#sha256',\n      signature_method\t: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'\n    }\n    result = saml.create(opts)\n\n    # Delete  the SAML Configuration\n    result = saml.delete\n\n    # Get the current SAML Configuration\n    result = saml.get\n\nAuthorizable Keystore:\n\n    keystore = aem.authorizable_keystore('/home/users/system', 'authentication-service')\n\n    # Create Keystore\n    keystore_password = 'password'\n    result = keystore.create(keystore_password)\n\n    # Change Keystore Password\n    old_keystore_password = 'old_password'\n    new_keystore_password = 'new_password'\n    change_password(old_keystore_password, new_keystore_password)\n\n    # Delete keystore\n    result = keystore.delete\n\n    # Delete Certificate Chain from Keystore\n    private_key_alias = 'alias_123'\n    delete_certificate_chain(private_key_alias)\n\n    # Download keystore to a specific F=file\n    opts = { file: '/root/saved_keystore.p12' }\n    result = keystore.download(**opts)\n\n    # Download keystore to a specific directory\n    opts = { path: '/root' }\n    result = keystore.download_keystore(**opts)\n\n    # Check if keystore exists\n    result = keystore.exists\n\n    # Check if Alias exists\n    private_key_alias = 'alias_123'\n    exists_certificate_chain(private_key_alias)\n\n    # Get info about an existing keystore\n    result = keystore.get\n\n    # Get info about an Certificate Chain in the Keystore\n    private_key_alias = 'alias_123'\n    get_certificate_chain(private_key_alias)\n\n    # Get info about an keystore provided as a file\n    file_path = '/root/store.p12'\n    keystore_password = 'admin'\n    result = keystore.read_keystore(file_path, keystore_password)\n\n    # Read certificate info from file\n    file_path = '/root/store.p12'\n    read_cert_from_file(file_path)\n\n    # Read certificate info provided as string\n    certificate_raw = '-----BEGIN CERTIFICATE-----\n    MIIEpDCABCDEFGHIJKLMNOPQRSTUVWXYZ\n    -----END CERTIFICATE-----'\n    def read_certificate_raw(certificate_raw)\n\n    # Upload a keystore backup\n    file_path = '/root/store.p12'\n    new_alias = alias_123\n    key_store_file_password = 'admin'\n    private_key_alias = 'alias_456'\n    private_key_password = 'private_password'\n\n    result = keystore.upload(file_path, new_alias, key_store_file_password, private_key_alias, private_key_password)\n\n    # Force upload a keystore backup\n    file_path = '/root/store.p12'\n    new_alias = alias_123\n    key_store_file_password = 'admin'\n    private_key_alias = 'alias_456'\n    private_key_password = 'private_password'\n    force = true\n\n    result = keystore.upload(file_path, new_alias, key_store_file_password, private_key_alias, private_key_password, force)\n\n    # Upload Certificate Chain into the Keystore\n    private_key_alias = 'alias_456'\n    certificate = '/tmp/cert.crt'\n    private_key = '/tmp/private_key.der'\n    upload_certificate_chain(private_key_alias, certificate, private_key)\n\n    # Upload Certificate Chain into the Keystore with certificate provided as string\n    certificate_raw = '-----BEGIN CERTIFICATE-----\n    MIIEpDCABCDEFGHIJKLMNOPQRSTUVWXYZ\n    -----END CERTIFICATE-----'\n    private_key_alias = 'alias_456'\n    private_key = '/tmp/private_key.der'\n    upload_certificate_chain_raw(private_key_alias, certificate_raw, private_key)\n\n    # Wait till keystore backup is uploaded\n    opts = {\n      file_path: '/root/saved_keystore.p12',\n      new_alias: alias_123,\n      key_store_file_password: 'admin',\n      private_key_alias: 'alias_456',\n      private_key_password: 'private_password',\n      _retries: {\n        max_tries: 60,\n        base_sleep_seconds: 2,\n        max_sleep_seconds: 2\n      }\n    }\n    result = keystore.upload_keystore_from_file_wait_until_ready(opts)\n\n    # Wait until Certificate Chain is uploaded into the keystore\n    opts = {\n      private_key_alias: 'alias_456',\n      certificate: '/tmp/cert.crt',\n      private_key: '/tmp/private_key.der',\n      _retries: {\n        max_tries: 60,\n        base_sleep_seconds: 2,\n        max_sleep_seconds: 2\n      }\n    }\n\n    upload_certificate_chain_from_file_wait_until_ready(opts)\n\n    # Wait until Certificate Chain is uploaded into the keystore with certificate provided as string\n    opts = {\n      private_key_alias: 'alias_456',\n      certificate_raw : '-----BEGIN CERTIFICATE-----\n      MIIEpDCABCDEFGHIJKLMNOPQRSTUVWXYZ\n      -----END CERTIFICATE-----',\n      private_key: '/tmp/private_key.der',\n      _retries: {\n        max_tries: 60,\n        base_sleep_seconds: 2,\n        max_sleep_seconds: 2\n      }\n    }\n\n    upload_certificate_chain_from_file_wait_until_ready(opts)\n\nTruststore:\n\n    truststore = aem.truststore\n\n    # Create Truststore\n    truststore_password = 'admin'\n    result = truststore.create_truststore(truststore_password)\n\n    # Delete Truststore\n    truststore_password = 'admin'\n    result = truststore.delete_truststore\n\n    # Download Truststore to a specific F=file\n    file = '/root/saved_truststore.p12'\n    result = truststore.download_truststore(file: file)\n\n    # Download Truststore to a specific directory\n    path = '/root'\n    result = truststore.download_truststore(path: path)\n\n    # Check if Truststore exists\n    result = truststore.exists_truststore\n\n    # Get info about an existing Truststore\n    result = truststore.get_truststore_info\n\n    # Get info about an Truststore provided as a file\n    opts = {\n      file_path: '/root/saved_truststore.p12'\n      truststore_password: 'admin',\n    }\n    result = truststore.read_truststore(opts)\n\n    # Upload a Truststore backup\n    file_path = '/root/saved_truststore.p12'\n    result = truststore.upload_truststore_from_file(file_path: file_path)\n\n    # Force upload a Truststore backup\n    opts = {\n      file_path: '/root/saved_truststore.p12'\n      force: true,\n    }\n    result = truststore.upload_truststore_from_file(opts)\n\n    # Wait till Truststore backup is uploaded\n    opts = {\n      file_path: '/root/saved_truststore.p12',\n      _retries: {\n        max_tries: 60,\n        base_sleep_seconds: 2,\n        max_sleep_seconds: 2\n      }\n    }\n    result = truststore.upload_truststore_from_file(opts)\n\n    # Force upload of a Truststore backup and wait till it is uploaded\n    opts = {\n      file_path: '/root/saved_truststore.p12',\n      force: true,\n      _retries: {\n        max_tries: 60,\n        base_sleep_seconds: 2,\n        max_sleep_seconds: 2\n      }\n    }\n    result = truststore.upload_truststore_from_file(opts)\n\n\nCertificate:\n\n    certificate = aem.truststore\n\n    # Delete Certificate via Truststore alias name\n    certalias = 'alias_1234'\n    result = certificate.delete_cert(certalias: certalias)\n\n    # Delete Certificate via serial number\n    serial = 1234567890\n    result = certificate.delete_cert(certalias: serial)\n\n    # Check if a Certificate exists via Truststore alias name\n    certalias = 'alias_1234'\n    result = certificate.exists_certs(certalias: certalias)\n\n    # Check if a Certificate exists via serial number\n    serial = 1234567890\n    result = certificate.exists_certs(certalias: serial)\n\n    # Export a certificate via serial number\n    opts = {\n      serial: 1234567890,\n      truststore_password: 'admin',\n    }\n    result = certificate.export_certificate(opts)\n\n    # Get a Certificate via Truststore alias name\n    certalias = 'alias_1234'\n    result = certificate.get_certificate(certalias: certalias)\n\n    # Get a Certificate via serial number\n    serial = 1234567890\n    result = certificate.get_certificate(certalias: serial)\n\n    # Read certificate info provided via string\n    certificate_raw = '-----BEGIN CERTIFICATE-----\n    MIIEpDCABCDEFGHIJKLMNOPQRSTUVWXYZ\n    -----END CERTIFICATE-----'\n    result = certificate.read_cert_raw(certificate_raw)\n\n    # Read certificate info provided via file\n    file_path = '/root/cert.crt'\n    result = certificate.read_cert_from_file(file_path)\n\n    # Upload a certificate provided via string\n    certificate_raw = '-----BEGIN CERTIFICATE-----\n    MIIEpDCABCDEFGHIJKLMNOPQRSTUVWXYZ\n    -----END CERTIFICATE-----'\n    result = certificate.upload_cert_raw(certificate_raw)\n\n    # Upload a certificate provided via file\n    file_path = '/root/cert.crt'\n    result = certificate.upload_cert_from_file(file_path)\n\n    # Upload a certificate via file and wait till it is uploaded\n    opts = {\n      file_path:'/root/cert.crt'\n      _retries: {\n        max_tries: 60,\n        base_sleep_seconds: 2,\n        max_sleep_seconds: 2\n      }\n    }\n    result = certificate.upload_cert_from_file_wait_until_ready(opts)\n\n    # Read certificate info provided via string and wait till it is uploaded\n    opts = {\n      certificate_raw = '-----BEGIN CERTIFICATE-----\n      MIIEpDCABCDEFGHIJKLMNOPQRSTUVWXYZ\n      -----END CERTIFICATE-----'\n      _retries: {\n        max_tries: 60,\n        base_sleep_seconds: 2,\n        max_sleep_seconds: 2\n      }\n    }\n    result = certificate.upload_cert_raw_wait_until_ready(opts)\n\nUser:\n\n    user = aem.user('/home/users/s/', 'someuser')\n\n    # create user\n    result = user.create('somepassword')\n\n    # check user's existence\n    result = user.exists\n\n    # set user permission\n    result = user.set_permission('/etc/replication', 'read:true,modify:true')\n\n    # change user password\n    result = user.change_password('somepassword', 'somenewpassword')\n\n    # add user to group\n    result = user.add_to_group('/home/groups/s/', 'somegroup')\n\n    # delete user\n    result = user.delete\n\nResult\n------\n\nEach of the above method calls returns a [RubyAem::Result](https://shinesolutions.github.io/ruby_aem/api/master/RubyAem/Result.html), which contains message, [RubyAem::Response](https://shinesolutions.github.io/ruby_aem/api/master/RubyAem/Response.html), and data payload. For example:\n\n    bundle = aem.bundle('com.adobe.cq.social.cq-social-forum')\n    result = bundle.stop\n    puts result.message\n    puts result.response.status_code\n    puts result.response.body\n    puts result.response.headers\n    puts result.data\n\nError Handling\n--------------\n\nAny API error will be thrown as [RubyAem::Error](https://shinesolutions.github.io/ruby_aem/api/master/RubyAem/Error.html) .\n\n    begin\n      bundle = aem.bundle('com.adobe.cq.social.cq-social-forum')\n      result = bundle.stop\n    rescue RubyAem::Error =\u003e e\n      puts e.message\n      puts e.result.response.status_code\n      puts e.result.response.body\n      puts e.result.response.headers\n      puts e.result.data\n    end\n\nTesting\n-------\n\nIntegration tests require an AEM instance with [Shine Solutions AEM Health Check](https://github.com/shinesolutions/aem-healthcheck) package installed.\n\nBy default it uses AEM running on http://localhost:4502 with `admin` username and `admin` password. AEM instance parameters can be configured using environment variables `aem_protocol`, `aem_host`, `aem_port`, `aem_username`, `aem_password`, and `aem_debug`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshinesolutions%2Fruby_aem","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshinesolutions%2Fruby_aem","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshinesolutions%2Fruby_aem/lists"}