node를 사용하여 Ghost 블로그를 자동으로 백업하는 방법js

37929 단어

TL:DR: If you're after the source code, check out this [Github Repository](https://github.com/tq-bit/ghost-backups.git):


고스트는 PHP가 구동하는 맏형 워드프레스와 같은 콘텐츠 관리 시스템이다.WP에서 거의 모든 것의 용례 (플러그인) 를 찾을 수 있지만, Ghost의 방법은 Node가 동력을 제공하는 효율적이다.js, 콘텐츠 창설에 전념하는 세분 시장 사용자를 대상으로 뛰어난 개발자 체험을 제공합니다.나는 최근에 그것으로 블로그를 쓰기 시작했는데, 그것이 제공하는 API를 매우 좋아한다.이것이 바로 내가 아래 항목을 세운 동기다.

Note: To follow along, you will need a dedicated instance of Ghost, including a domain and admin access. In case you don't have one but would still like to explore its functionality, there's a demo for the content endpoint. It's read-only, but it'll do. https://demo.ghost.io/ghost/api/v3/content/posts/?key=22444f78447824223cefc48062


선결 조건


당신이 따라야 할 최저 요구는 기계에 설치된 Node.js 작업 실례입니다.나는 Raspberry Pi 3+를 사용하여 백업을 하고 있다. 백업을 저장하는 경로를 알 수 있지만, 다른 컴퓨터도 괜찮다.위에서 설명한 대로 Ghost 인스턴스가 없는 경우에도 사용할 수 있습니다demo.

요컨대 유령 API


고스트는 itheadless CMS를 통해 무두REST admin-endpoint로 조작할 수 있다.읽기 전용content-endpoint도 제공하지만 전자는 어떤 클라이언트를 사용하든 내용에 대해 다양한 방법을 포함한다.그러나 소통을 하려면 몇 가지 간단한 절차가 필요하다.

새로운 Ghost 통합 설정


If you are following along for the content-endpoint, skip this step.

  • Ghost 관리 패널에서 통합 > 사용자 정의 통합 추가
  • 통합 이름을 지정하고 생성합니다.
  • 이 완료되면 두 개의 API 키가 생성됩니다.사용자 자격 증명이 없는 상태에서 Ghost 플랫폼에 대한 접근을 제공하기 때문에 이 키들을 누구와도 공유하지 마십시오.
  • 만약에 콘텐츠 endpoint를 사용한다면 콘텐츠 키를 URL 조회 매개 변수로 즉시 사용할 수 있습니다. 위의 설명과 같이 다음과 같은 GET 요청을 보낼 수 있습니다.
  • # Make a curl GET request to the ghost demo endpoint in your terminal
    $ curl https://demo.ghost.io/ghost/api/v3/content/posts/?key=22444f78447824223cefc48062
    
  • 우리의 예에서 관리자 키를 기록해야 합니다. 왜냐하면 우리는 잠시 후에 그것을 사용하여 Json Webtoken 신분 검증을 할 것입니다.
  • 이 완료되면 IDE로 이동하여 로직을 작성합니다.
  • 개시하다


    프로젝트 구조 준비


    선택한 디렉터리로 전환하고 새 npm 항목을 초기화합니다:
    $ cd /path/to/project
    $ npm init -y
    
    다음은 우리 응용 프로그램의 기본 구조를 설정합니다.
  • 두 개의 새 디렉터리를 만듭니다.
    하나는 구성이고 다른 하나는 서비스입니다.
  • config 디렉토리에 두 개의 파일을 생성합니다.
    설정이라고 합니다.js, 또 다른 util.js
  • 서비스 디렉터리에backupContent라는 파일을 만듭니다.js
  • 루트 디렉터리에 index라는 파일을 만듭니다.js
  • 이 작업을 완료하면 다음 npm 패키지를 설치합니다.
  • chalk컬러 콘솔 출력용
  • isomorphic-fetchhttp 요청(선택 사항: node-fetch,axios)
  • jsonwebtoken 관리 엔드포인트
  • 에 대한 인증
  • rimraf 오래된 백업 저장 및 삭제
  • npm i chalk isomorphic-fetch jsonwebtoken rimraf
    
    마지막으로, 코드를 실행하기 위해 npm 스크립트를 추가합니다.
    # Add to your package.json file
    {
     [...],
     scripts: {
      "start": "node index"
     },
     [...]
    }
    
    만일 모든 것이 순조롭다면, 당신의 프로젝트는 현재 아래의 구조와 모든 관련 노드 모듈을 설치할 것입니다. 우리는 계속 전진하고 생명을 부여할 준비가 되어 있습니다.
    /
    | - config
    | | - config.js
    | | - util.js
    | - node_modules
    | - services
    | | - backupContent.js
    | - index.js
    

    구성 설정


    다음 코드를 설정에 추가합니다.js 파일.여기서 정의한 변수는 다음과 같은 용도로 API 및 백업 구성 정보를 포함합니다.
    변수 이름
    의 목적
    고스타피울
    ghost 관리 종결점의 루트 경로
    ghostApiPaths
    백업 데이터를 가져올 경로
    고스타틴기
    이전에 기록한 통합 관리 키
    백업 간격
    업데이트 간격(밀리초)
    백업 경로
    백업 컴퓨터에 저장된 데이터의 경로
    시간을 거슬러 올라가다
    오래된 업데이트를 보존하는 데 몇 달이 걸린다
    genBackupDirPath
    고유 백업 디렉토리 생성 함수
    const { join } = require('path');
    
    // Configure the relevant API params
    const ghostApiUrl = 'https://<your-ghost-domain>/ghost/api/v3/admin';
    // If you are following along with the demo, uncomment the following: 
    // const ghostApiUrl = 'https://demo.ghost.io/ghost/api/v3/content/'
    // Also, make sure to append the query in the service that utilizes this parameter
    const ghostApiPaths = ['posts', 'pages', 'site', 'users'];
    const ghostAdminKey = '<your-admin-key>';
    
    // Configure the backup settings
    const backupInterval = 1000 /* Miliseconds */ * 60 /* Seconds */ * 60 /* Minutes */ * 24 /* Hours*/ * 1; /* Days */
    const backupDirPath = '/home/pi/Backups'; 
    const backupLifetime = 4; /* Months */
    const genBackupDirPath = () => {
     const date = new Date();
    
     const year = date.getFullYear();
     const month = (date.getMonth() + 1).toString().padStart(2, 0);
     const day = date.getDate().toString().padStart(2, 0);
     const hour = date.getHours().toString().padStart(2, 0);
     const min = date.getMinutes().toString().padStart(2, 0);
     const now = `${year}_${month}_${day}-${hour}_${min}`;
    
     return join(backupDirPath, now);
    };
    
    module.exports = {
     ghostApiUrl,
     ghostApiPaths,
     ghostAdminKey,
     backupInterval,
     backupLifetime,
     backupDirPath,
     genBackupDirPath,
    };
    
    

    유틸리티 함수 준비


    백업 서비스에는 두 가지 유틸리티가 필요합니다.
  • 인증을 처리하는 데 사용됩니다.Ghost 관리 노드는 Json Webtoken 인증을 사용하여 통합에서 받은 관리 키를 가지고 있습니다validation signature.
  • 이전 백업의 삭제를 처리하는 데 사용되는 또 다른 기능그것은 백업 주기마다 한 번씩 호출될 것이다.
  • 다음 내용을 util에 추가합니다.js 파일:
    // Import the necessary functions
    const { promises } = require('fs');
    const { sign } = require('jsonwebtoken');
    const rimraf = require('rimraf');
    
    /**
     * @desc    Create the authorization header to authenticate against the
     *          Ghost admin-endpoint
     *
     * @param   {String} ghostAdminKey
     * 
     * @returns {Object} The headers to be appended to the http request
     */
    function genAdminHeaders(ghostAdminKey) {
     // Extract the secret from the Ghost admin key
     const [id, secret] = ghostAdminKey.split(':');
    
     // Create a token with an empty payload and encode it.
     const token = sign({}, Buffer.from(secret, 'hex'), {
      keyid: id,
      algorithm: 'HS256',
      expiresIn: '5m',
      audience: '/v3/admin',
     });
    
     // Create the headers object that's added to the request
     const headers = {Authorization: `Ghost ${token}`}
    
     return headers;
    }
    
    /**
     * @desc    Delete backup directories that are older than the backupLifetime.
     *
     * @param   {String} dirPath The path to where backups are being stored
     * @param   {Number} backupLifetime The amount of months a backup is to be stored
     * 
     * @returns {Array} An array of paths that have been deleted 
     */
    async function deleteOldDirectories(dirPath, backupLifetime) {
     const dir = await promises.opendir(dirPath);
     let deletedBackups = [];
    
     for await (const dirent of dir) {
      const date = new Date();
      const year = date.getFullYear();
      const month = date.getMonth() + 1;
    
      // For each backup entry, extract the year and month in which is was created
      const createdYear = +dirent.name.split('_')[0];
      const createdMonth = +dirent.name.split('_')[1];
    
      // In case backup was made this year,
      // check if createdMonth + lifetime are less than current month
      if (createdYear === year) {
       if (createdMonth - backupLifetime >= month) {
        deletedBackups.push(dirent.name)
        rimraf.sync(`${dirPath}/${dirent.name}`)
       }
      }
    
      // In case backup was made last year,
      // check if createdMonth + lifetime is smaller than 12
      else {
       if (createdMonth + backupLifetime <= 12) {
        deletedBackups.push(dirent.name)
        rimraf.sync(`${dirPath}/${dirent.name}`)
       }
      }
     }
     return deletedBackups;
    }
    
    module.exports = { getAdminHeaders, deleteOldDirectories };
    
    

    쓰기 서비스


    다음 내용을 백업 내용에 추가합니다.js 파일.무슨 일이 일어났는지 알고 싶으면 코드의 주석을 고려하십시오.
    // Load relevant core package functions
    const { join } = require('path');
    const { mkdirSync, writeFileSync } = require('fs');
    const { log } = require('console');
    
    // Load npm modules
    const fetch = require('isomorphic-fetch');
    const { red, yellow, green } = require('chalk');
    
    // Load config and utility functions
    const { ghostApiUrl, ghostApiPaths, ghostAdminKey, backupDirPath, genBackupDirPath, backupLifetime } = require('../config/config');
    const { genAdminHeaders, deleteOldDirectories } = require('../config/util');
    
    // Main function for the backup service
    async function runBackupContent() {
     log(green(`⏳ Time to backup your content. Timestamp: ${new Date()}`));
     try {
      // Generate the backup directory and the authorization headers
      const dir = genBackupDirPath();
      const headers = genAdminHeaders(ghostAdminKey);
    
      // e.g. /home/pi/Backups/2021_01_02-13_55/site.json
      mkdirSync(dir);
    
      // Check for old backups and clean them up
      const deleted = deleteOldDirectories(backupDirPath, backupLifetime);
      if (deleted.length > 0) {
       deleted.forEach(deletedBackup => log(yellow(`☝ Deleted backup from ${deletedBackup}`)));
      } else {
       log(green('✅ No old backups to be deleted'));
      }
    
      // Make a backup for all the content specified in the api paths config
      ghostApiPaths.forEach(async path => {
       // Create the relevant variables
    
       // The endpoint from where to receive backup data
       // e.g. https://<your-ghost-domain>/ghost/api/v3/admin/posts
       const url = `${ghostApiUrl}/${path}`; 
    
       // These options.headers will hold the Json Webtoken
       // e.g. 'Authorization': 'Ghost eybf12bf.8712dh.128d7g12'
       const options = { method: 'get', timeout: 1500, headers: { ...headers } };
    
       // The name of the corresponding backup file in json
       // e.g. posts.json
       const filePath = join(dir, path + '.json');
       const response = await fetch(url, options);
    
       // If the http status is unexpected, log the error down in a separate file
       if (response.status !== 200) {
        const errPath = join(dir, path + 'error.json');
        const data = await response.json();
        writeFileSync(errPath, jsonStringify(data));
        log(red(`❌ ${new Date()}: Something went wrong while trying to backup ${path}`));
    
       // If the http status is returned as expected, write the result to a file
       } else {
        const data = await response.json();
        writeFileSync(filePath, JSON.stringify(data));
        log(green(`✅ Wrote backup for '${path}' - endpoint into ${filePath}`));
       }
      });
     } catch (e) {
      // Do appropriate error handling based on the response code
      switch (e.code) {
       // If, for some reason, two backups are made at the same time
       case 'EEXIST':
        log(red('❌ Tried to execute a backup for the same time period twice. Cancelling ... '));
        break;
       default:
        log(red(e.message));
        break;
      }
     }
    }
    
    module.exports = runBackupContent;
    
    

    서비스 완료 및 실행


    우리가 이룩한 성과를 간단명료하게 회고합시다
  • Ghost 블로그를 정기적으로 백업하는 서비스를 만들었습니다.
  • 이를 위해 Ghost의 REST 관리 노드와 Json Webtoken 인증
  • 을 사용합니다.
  • 백업은 사설 폴더에 저장되며 4개월 이상의 백업은 자동으로 삭제됩니다
  • .
    이제 이 서비스를 색인에 가져옵니다.js 파일 및 실행
    // Load node modules
    const { log } = require('console');
    const { green } = require('chalk');
    
    // Load the backup interval from the configuration
    const { backupInterval } = require('./config/config');
    
    // Load the service
    const runBackupContent = require('./services/backupContent');
    
    // Start the service with the given interval
    log(green(`⏳ Scheduled backup job with a ${backupInterval / (1000 * 60 * 60)} - hour interval`));
    runBackupContent();
    setInterval(() => runBackupContent(), backupInterval);
    
    
    그런 다음 콘솔에서 시작 스크립트를 실행합니다.
    $ npm run start 
    # Or run index directly
    $ node index
    
    다른 모든 것이 순조롭다고 가정하면 다음 콘솔 출력을 볼 수 있습니다.

    이제 당신은 그것을 가지고 있습니다. 기본적이지만 효과적인 블로그 게시물 백업 서비스입니다.

    다음 단계 및 추가 기능


    다음과 같은 기능도 포함될 수 있습니다.
  • 최신 업데이트를 자동으로 읽고 관리 노드에 여러 개의 POST 요청(경로마다 하나)을 보내서 잃어버린 페이지와 게시물을 복구하는 서비스입니다.
  • 이 서비스를 실행하고 감시하기 위해 pm2 module를 사용합니다.
  • 데이터베이스 또는 .env 변수에 설정을 넣고 깔끔한 모니터링api를 구축한다.
  • 백업 성공 및/또는 실패 시 telegram bot를 사용하여 휴대폰에 메시지를 보냅니다.
  • This post was originally published at https://q-bit.me/how-to-make-backups-of-your-ghost-blog-with-node-js/


    읽어주셔서 감사합니다.이 글이 마음에 드시면 트위터에 연락 주세요.🐤

    좋은 웹페이지 즐겨찾기