Really need help with Custom Storage Adapter

Can someone help me with instructions to set up a custom storage adapter in Ghost?

I am running a custom server created with ghost-cli with the latest version. I followed the instructions to a T from the ghost docs, but they are outdated and cause errors when booting the ghost server after updating the configuration. Furthermore, all of the custom adapters, including the recommended S3 one that can be installed via npm, are not working. I really think the best approach here is to just find a working solution and build from there, but even the most basic implementations fail satisfy javascript importing requirements.

Here is my storage adapter code:

const BaseAdapter = require("ghost-storage-base");
const AWS = require('aws-sdk');
const { v4: uuidv4 } = require('uuid');
const path = require('path');
const mime = require('mime-types');

class S3CustomAdapter extends BaseAdapter {
  constructor(config) {
    super();
    this.s3 = new AWS.S3({
      accessKeyId: config.key,
      secretAccessKey: config.secret,
      region: config.region,
    });
    this.bucket = config.bucket;
  }

  save(image) {
    return new Promise((resolve, reject) => {
      const fileName = `${uuidv4()}${path.extname(image.name)}`;
      const params = {
        Bucket: this.bucket,
        Key: fileName,
        Body: image.stream,
        ContentType: mime.lookup(image.name),
        ACL: 'public-read',
      };

      this.s3.upload(params, (err, data) => {
        if (err) {
          return reject(err);
        }
        resolve(data.Location);
      });
    });
  }

  exists(fileName) {
    const params = {
      Bucket: this.bucket,
      Key: fileName,
    };

    return this.s3
      .headObject(params)
      .promise()
      .then(() => true)
      .catch((err) => {
        if (err.code === 'NotFound') {
          return false;
        }
        throw err;
      });
  }

  serve() {
    return (req, res, next) => {
      const fileName = req.path.replace(/^\//, '');
      const params = {
        Bucket: this.bucket,
        Key: fileName,
      };

      this.s3.getObject(params)
        .createReadStream()
        .on('error', (err) => {
          if (err.code === 'NoSuchKey') {
            return res.status(404).end();
          }
          next(err);
        })
        .pipe(res);
    };
  }

  delete(fileName) {
    const params = {
      Bucket: this.bucket,
      Key: fileName,
    };

    return this.s3
      .deleteObject(params)
      .promise()
      .then(() => true)
      .catch((err) => {
        if (err.code === 'NoSuchKey') {
          return false;
        }
        throw err;
      });
  }

  read(fileName) {
    const params = {
      Bucket: this.bucket,
      Key: fileName,
    };

    return this.s3
      .getObject(params)
      .promise()
      .then((data) => data.Body)
      .catch((err) => {
        if (err.code === 'NoSuchKey') {
          return null;
        }
        throw err;
      });
  }
}

module.exports = S3CustomAdapter;

If this is no longer supported I think it would be greatly appreciated it if the community were made aware.

Thanks!

You are right that both the adapters here appear outdated:

Repairing this may involve some coding skills to investigate what needs to be updated about these older adapters.

It’s likely that Ghost Pro is using a storage adapter in production so general support for storage adapters should still be working.

1 Like

Is there some code I can reference somewhere? Have you implemented it recently?

Be it that it may be supported in production, I can’t find any working examples or get it to work by myself. Can you help me figure out what I need to do?

Have you found this reference?

This storage adapter was updated last year:

@Kevin Both the recommended S3 adapters are rather outdated. It could be good to refresh the docs with some more up to date alternatives.

Looking at the Github “Network” of ghost-storage-adapter-s3, there are several forks that have been updated more recently.

Perhaps if the Ghost project hosted this project some of these people would collaborate in one place. Right now it looks like a lot of people are forking and modifying the project, but there isn’t one central place that has most of the fixes, which is unfortunate.

1 Like

We need this or whatever they use in production, which could be GCS now because of activity pub.
Relying on local storage will only get you that far. For long term it is best to have alternatives that are supported.

I don’t think Ghost Pro uses any storage adapters (especially for images), because one big downside of using a storage adapter is that you can’t use {{img_url}} anymore. If you write images somewhere other than /content/images folder, auto resize feature doesn’t work anymore.

I would like to have a more advanced img_url implementation that can work with custom storages as well. Actually, this is in my agenda to work on a PR for that, but unfortunately I can’t do it very soon.