diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9e69758bfed..df762654135 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -491,7 +491,9 @@ jobs: package: ${{ matrix.js-package }} # The platform test suite's default command drives a live network, so it # runs from the E2E jobs. Its unit tests need nothing and run here. - test-command: ${{ (matrix.js-package == 'dashmate' || contains(matrix.js-package, 'platform-test-suite')) && 'test:unit' || 'test' }} + # Dashmate also runs its integration tests here: they need Docker, which + # this runner has, but not a live network. + test-command: ${{ matrix.js-package == 'dashmate' && 'test:ci' || (contains(matrix.js-package, 'platform-test-suite') && 'test:unit' || 'test') }} direct-packages: ${{ needs.changes.outputs.js-packages-direct }} js-deps-versions: diff --git a/.pnp.cjs b/.pnp.cjs index 062aa0f7409..f2c82efb91a 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -9698,6 +9698,7 @@ const RAW_RUNTIME_STATE = ["oclif", "npm:4.0.3"],\ ["pretty-bytes", "npm:5.6.0"],\ ["pretty-ms", "npm:7.0.1"],\ + ["proper-lockfile", "npm:4.1.2"],\ ["public-ip", "npm:6.0.1"],\ ["qs", "npm:6.15.0"],\ ["rxjs", "npm:6.6.7"],\ @@ -9707,7 +9708,8 @@ const RAW_RUNTIME_STATE = ["systeminformation", "npm:5.31.1"],\ ["table", "npm:6.8.1"],\ ["tar", "npm:7.5.10"],\ - ["wrap-ansi", "npm:7.0.0"]\ + ["wrap-ansi", "npm:7.0.0"],\ + ["write-file-atomic", "npm:5.0.1"]\ ],\ "linkType": "SOFT"\ }]\ @@ -18295,6 +18297,16 @@ const RAW_RUNTIME_STATE = ["signal-exit", "npm:3.0.7"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:4.1.2", {\ + "packageLocation": "./.yarn/cache/proper-lockfile-npm-4.1.2-a140a3c928-000a4875f5.zip/node_modules/proper-lockfile/",\ + "packageDependencies": [\ + ["graceful-fs", "npm:4.2.10"],\ + ["proper-lockfile", "npm:4.1.2"],\ + ["retry", "npm:0.12.0"],\ + ["signal-exit", "npm:3.0.7"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["protobufjs", [\ @@ -23536,6 +23548,15 @@ const RAW_RUNTIME_STATE = ["write-file-atomic", "npm:4.0.1"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:5.0.1", {\ + "packageLocation": "./.yarn/cache/write-file-atomic-npm-5.0.1-52283db6ee-648efddba5.zip/node_modules/write-file-atomic/",\ + "packageDependencies": [\ + ["imurmurhash", "npm:0.1.4"],\ + ["signal-exit", "npm:4.1.0"],\ + ["write-file-atomic", "npm:5.0.1"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["ws", [\ diff --git a/.yarn/cache/proper-lockfile-npm-4.1.2-a140a3c928-000a4875f5.zip b/.yarn/cache/proper-lockfile-npm-4.1.2-a140a3c928-000a4875f5.zip new file mode 100644 index 00000000000..a7389ae2254 Binary files /dev/null and b/.yarn/cache/proper-lockfile-npm-4.1.2-a140a3c928-000a4875f5.zip differ diff --git a/.yarn/cache/write-file-atomic-npm-5.0.1-52283db6ee-648efddba5.zip b/.yarn/cache/write-file-atomic-npm-5.0.1-52283db6ee-648efddba5.zip new file mode 100644 index 00000000000..4b1680c8f31 Binary files /dev/null and b/.yarn/cache/write-file-atomic-npm-5.0.1-52283db6ee-648efddba5.zip differ diff --git a/packages/dashmate/configs/defaults/getBaseConfigFactory.js b/packages/dashmate/configs/defaults/getBaseConfigFactory.js index fbcbbc15dc7..7a2c866122f 100644 --- a/packages/dashmate/configs/defaults/getBaseConfigFactory.js +++ b/packages/dashmate/configs/defaults/getBaseConfigFactory.js @@ -6,6 +6,7 @@ import Config from '../../src/config/Config.js'; import { NETWORK_MAINNET, PACKAGE_ROOT_DIR, + LETSENCRYPT_ACME_DIRECTORY_URL, } from '../../src/constants.js'; const { version } = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT_DIR, 'package.json'), 'utf8')); @@ -255,6 +256,7 @@ export default function getBaseConfigFactory() { }, letsencrypt: { email: null, + acmeDirectoryUrl: LETSENCRYPT_ACME_DIRECTORY_URL, }, }, }, diff --git a/packages/dashmate/configs/getConfigFileMigrationsFactory.js b/packages/dashmate/configs/getConfigFileMigrationsFactory.js index 23fe3f0fbaa..be7f3f340fa 100644 --- a/packages/dashmate/configs/getConfigFileMigrationsFactory.js +++ b/packages/dashmate/configs/getConfigFileMigrationsFactory.js @@ -1701,6 +1701,24 @@ export default function getConfigFileMigrationsFactory(homeDir, defaultConfigs) return configFile; }, + '4.2.0': (configFile) => { + // The ACME directory certificates are requested from became + // configurable. Existing configs have no value for it, and the schema + // requires one, so fill in the directory they were already using. + Object.entries(configFile.configs) + .forEach(([, options]) => { + const providerConfigs = options.platform?.gateway?.ssl?.providerConfigs; + + if (providerConfigs?.letsencrypt + && providerConfigs.letsencrypt.acmeDirectoryUrl === undefined) { + providerConfigs.letsencrypt.acmeDirectoryUrl = base.get( + 'platform.gateway.ssl.providerConfigs.letsencrypt.acmeDirectoryUrl', + ); + } + }); + + return configFile; + }, }; } diff --git a/packages/dashmate/docs/config/gateway.md b/packages/dashmate/docs/config/gateway.md index 7a5a3a92da5..90581e39d69 100644 --- a/packages/dashmate/docs/config/gateway.md +++ b/packages/dashmate/docs/config/gateway.md @@ -142,8 +142,27 @@ These settings control SSL/TLS for secure connections: | `platform.gateway.ssl.providerConfigs.zerossl.apiKey` | ZeroSSL API key | `null` | `"your-api-key"` | | `platform.gateway.ssl.providerConfigs.zerossl.id` | ZeroSSL certificate ID | `null` | `"certificate_id"` | +### Let's Encrypt Provider Configuration + +| Option | Description | Default | Example | +|--------|-------------|---------|---------| +| `platform.gateway.ssl.providerConfigs.letsencrypt.email` | Contact address for expiry notices | `null` | `"admin@example.com"` | +| `platform.gateway.ssl.providerConfigs.letsencrypt.acmeDirectoryUrl` | ACME directory certificates are requested from | `https://acme-v02.api.letsencrypt.org/directory` | `https://acme-staging-v02.api.letsencrypt.org/directory` | + +A node is identified by its external IP rather than a domain name, and Let's Encrypt +issues IP address certificates only under its short-lived profile, so these +certificates are valid for about six days and are renewed automatically by the +Dashmate helper. + +Point `acmeDirectoryUrl` at the staging directory to rehearse issuance. Production +allows only a few failed validations per hour, and the usual cause of failure — +inbound port 80 being unreachable — takes several attempts to sort out. Certificates +from staging are not publicly trusted, so set it back afterwards and obtain again +with `dashmate ssl obtain --force`. + Available SSL providers: - `zerossl`: Commercial certificate provider with automated issuance +- `letsencrypt`: Free certificates issued over ACME; requires inbound port 80 during issuance and renewal - `selfSigned`: Self-signed certificates (not trusted by browsers) - `file`: Use existing certificate files (requires certificate and key files to be manually provided) diff --git a/packages/dashmate/docs/config/index.md b/packages/dashmate/docs/config/index.md index a81ac218c61..adbb6d0e5ac 100644 --- a/packages/dashmate/docs/config/index.md +++ b/packages/dashmate/docs/config/index.md @@ -179,3 +179,71 @@ dashmate config get