Skip to content

🐛 Fix: the HTTP-01 challenge directory created by acme.sh may block subsequent uninstall.#2060

Open
southp wants to merge 1 commit intoTryGhost:mainfrom
southp:fix/pre-create-acme-working-directory
Open

🐛 Fix: the HTTP-01 challenge directory created by acme.sh may block subsequent uninstall.#2060
southp wants to merge 1 commit intoTryGhost:mainfrom
southp:fix/pre-create-acme-working-directory

Conversation

@southp
Copy link
Copy Markdown

@southp southp commented Mar 10, 2026

Summary

Ghost CLI uses acme.sh to perform HTTP-01 challenge for automating SSL provisioning. The script is run by root, and will create its own working directory, system/nginx-root, if it doesn't exist. That means the directory will also owned by root. Thus, when a user performs ghost uninstall later, the process may fail as they'd likely do it using a non-root account.

This PR fixes the issue by pre-creating the web root directory, since the directory doesn't really need to be owned by the root.

Test plan

  1. ./node_modules/.bin/mocha --recursive extensions/**/test should pass.
  2. On a server with a mapped domain,
    1. Install a ghost instance through ghost install under a non-root account.
    2. Make sure the SSL provisioning has succeeded.
    3. Uninstalling it by running ghost uninstall should succeed.

Not sure if there is any better way of testing this. It would be great if there is a way to run the test suite partially through yarn test and a way to test the server setup part in a local sandboxed environment. These both are out of scope of this PR, though :)


Note

Low Risk
Small, localized change to the SSL setup task list plus test updates; no changes to certificate generation logic or auth/security behavior beyond directory creation.

Overview
Prevents ghost uninstall failures after SSL provisioning by creating system/nginx-root up front during setupSSL, ensuring it isn’t first created/owned by root when acme.sh runs.

Updates the Nginx extension unit tests to cover the new “create web root directory” listr task and adjusts task indices for subsequent SSL setup steps (acme.generate, dhparam, headers, config, restart).

Written by Cursor Bugbot for commit 6c55d06. This will update automatically on new commits. Configure here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 10, 2026

Walkthrough

This PR inserts a new step into the Nginx extension SSL setup sequence that ensures the web root directory exists (calls fs.ensureDir(rootPath)) after "Installing acme.sh" and before "Getting SSL Certificate from Let's Encrypt". A unit test was added to assert the directory creation, and existing tests were shifted to account for the new task index.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: fixing an issue where the HTTP-01 challenge directory created by acme.sh may block subsequent uninstall operations.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining the problem, the solution, and providing a comprehensive test plan.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
extensions/nginx/test/extension-spec.js (1)

403-412: Consider asserting the path argument for better test coverage.

The test verifies that ensureDir is called, but doesn't verify it's called with the correct rootPath. This would make the test more robust against regressions.

🔧 Suggested improvement
             return tasks[3].task().then(() => {
                 expect(ensureDirStub.calledOnce).to.be.true;
+                expect(ensureDirStub.calledWithExactly('/var/www/ghost/system/nginx-root')).to.be.true;
             });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extensions/nginx/test/extension-spec.js` around lines 403 - 412, The test
currently checks that ensureDir was called but not that it was called with the
expected web root path; update the assertion after calling tasks[3].task() to
verify ensureDirStub was invoked with the correct rootPath value (use the same
rootPath used by proxyNginx/getTasks or derive it from ext), e.g. assert
ensureDirStub.calledOnceWith(expectedRootPath). Locate the ensureDir stub
assignment (proxy['fs-extra'].ensureDir = ensureDirStub), the proxyNginx(proxy)
call, and the tasks[3].task() invocation to add the calledWith assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@extensions/nginx/test/extension-spec.js`:
- Around line 403-412: The test currently checks that ensureDir was called but
not that it was called with the expected web root path; update the assertion
after calling tasks[3].task() to verify ensureDirStub was invoked with the
correct rootPath value (use the same rootPath used by proxyNginx/getTasks or
derive it from ext), e.g. assert ensureDirStub.calledOnceWith(expectedRootPath).
Locate the ensureDir stub assignment (proxy['fs-extra'].ensureDir =
ensureDirStub), the proxyNginx(proxy) call, and the tasks[3].task() invocation
to add the calledWith assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5356aabc-145f-4ddf-8141-6251c649d54b

📥 Commits

Reviewing files that changed from the base of the PR and between ca2dbf1 and cb972e4.

📒 Files selected for processing (2)
  • extensions/nginx/index.js
  • extensions/nginx/test/extension-spec.js

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Free Tier Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

…g up.

`acme.sh` is run by `root` and will create the working directory, i.e. webroothere, used for acme challenge if it doesn't exist. When that happens, the webroot directory will be owned by root, making the subsequent `ghost uninstall` fail. This commit precreates that directory before running `acme.sh`, so the subsequent clean-up can be carried out as expected.
@southp southp force-pushed the fix/pre-create-acme-working-directory branch from cb972e4 to 6c55d06 Compare March 10, 2026 02:00
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
extensions/nginx/test/extension-spec.js (1)

422-422: Consider resolving setup tasks by title instead of array index.

Adding one task required renumbering a large part of this suite. Looking tasks up by title would make future sequencing changes much less brittle.

♻️ Possible cleanup
 function getTasks(ext, argv = {}) {
     ext.setupSSL(Object.assign({argv}, ctx));

     expect(ext.ui.log.called, 'getTasks: ui.log').to.be.false;
     expect(ext.ui.listr.calledOnce, 'getTasks: ui.listr').to.be.true;
-    return ext.ui.listr.args[0][0];
+    const tasks = ext.ui.listr.args[0][0];
+    return {
+        all: tasks,
+        byTitle(title) {
+            return tasks.find(task => task.title === title);
+        }
+    };
 }
-const tasks = getTasks(ext, {sslemail: 'test@example.com', sslstaging: true});
-return tasks[4].task().then(() => {
+const {byTitle} = getTasks(ext, {sslemail: 'test@example.com', sslstaging: true});
+return byTitle('Getting SSL Certificate from Let\'s Encrypt').task().then(() => {

Also applies to: 446-448, 460-461, 482-483, 495-496, 528-528, 547-547, 561-561, 576-576

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extensions/nginx/test/extension-spec.js` at line 422, The tests are brittle
because they call setup tasks by numeric indices (e.g., tasks[4].task()) —
change these to find the task object by its title and invoke its task() method
instead (e.g., tasks.find(t => t.title === 'Your Task Title').task()). Update
every occurrence where tasks is indexed (including the other instances noted) to
use a title-based lookup and add a small assertion or guard that the found task
exists before calling .task() to avoid runtime errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@extensions/nginx/test/extension-spec.js`:
- Line 422: The tests are brittle because they call setup tasks by numeric
indices (e.g., tasks[4].task()) — change these to find the task object by its
title and invoke its task() method instead (e.g., tasks.find(t => t.title ===
'Your Task Title').task()). Update every occurrence where tasks is indexed
(including the other instances noted) to use a title-based lookup and add a
small assertion or guard that the found task exists before calling .task() to
avoid runtime errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a435c36d-6708-44ea-96d2-18aa8ced16d7

📥 Commits

Reviewing files that changed from the base of the PR and between cb972e4 and 6c55d06.

📒 Files selected for processing (2)
  • extensions/nginx/index.js
  • extensions/nginx/test/extension-spec.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant