diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea95f957..5b92a3d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,144 +63,3 @@ jobs: uses: softprops/action-gh-release@v2 with: files: pfSense-${{ matrix.PFSENSE_VERSION }}-pkg-RESTAPI.pkg - - build_schemas: - runs-on: self-hosted - needs: [release_pkg] - steps: - - uses: actions/checkout@v4 - - - uses: actions/download-artifact@v4 - with: - name: pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-pkg-RESTAPI.pkg - path: pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-pkg-RESTAPI.pkg - - - name: Setup pfSense VM - run: | - /usr/local/bin/VBoxManage controlvm pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE poweroff || true - /usr/local/bin/VBoxManage snapshot pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE restore initial - /usr/local/bin/VBoxManage startvm pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE --type headless - sleep 5 - - # This is only necessary until GitHub Actions allows an easier way to get a URL to download the artifact within pfSense - - name: Copy pfSense-pkg-RESTAPI build to pfSense - run: | - pfsense-vshell --host pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE.jaredhendrickson.com -u admin -p pfsense -c 'pfSsh.php playback enablesshd' -k - pfsense-vshell --host pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE.jaredhendrickson.com -u admin -p pfsense -c "mkdir /root/.ssh/ && echo $(cat ~/.ssh/id_rsa.pub) > /root/.ssh/authorized_keys" -k - scp -o StrictHostKeyChecking=no pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-pkg-RESTAPI.pkg/pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-pkg-RESTAPI.pkg admin@pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE.jaredhendrickson.com:/tmp/ - - - name: Install pfSense-pkg-RESTAPI on pfSense - run: | - ssh -o StrictHostKeyChecking=no -o LogLevel=quiet admin@pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE.jaredhendrickson.com "pkg -C /dev/null add /tmp/pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-pkg-RESTAPI.pkg" - sleep 5 - - - name: Fetch schemas from pfSense - run: | - curl -s -k -u admin:pfsense -X GET https://pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE.jaredhendrickson.com/api/v2/schema/openapi > openapi.json - curl -s -k -u admin:pfsense -X GET https://pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE.jaredhendrickson.com/api/v2/schema/graphql > schema.graphql - - - name: Teardown pfSense VM - if: "${{ always() }}" - run: | - /usr/local/bin/VBoxManage controlvm pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE poweroff || true - /usr/local/bin/VBoxManage snapshot pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE restore initial - - - name: Upload OpenAPI schema - uses: actions/upload-artifact@v4 - with: - name: openapi.json - path: openapi.json - - - name: Upload GraphQL schema - uses: actions/upload-artifact@v4 - with: - name: schema.graphql - path: schema.graphql - - release_docs: - needs: [build_schemas] - runs-on: ubuntu-latest - if: ${{ !github.event.release.prerelease }} - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - uses: actions/checkout@v4 - - - name: Setup Pages - uses: actions/configure-pages@v5.0.0 - - - name: Make website directory - run: mkdir ./www - - - name: Setup python - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Build mkdocs site - run: | - python3 -m pip install -r ./requirements.txt - python3 -m mkdocs build - mv ./site/* ./www/ - - - name: Download OpenAPI schema - uses: actions/download-artifact@v4 - with: - name: openapi.json - path: openapi.json - - - name: Download GraphQL schema - uses: actions/download-artifact@v4 - with: - name: schema.graphql - path: schema.graphql - - - name: Build Swagger UI - run: | - mkdir -p ./www/api-docs/ - wget -O /tmp/swagger.tar.gz https://github.com/swagger-api/swagger-ui/archive/refs/tags/v${{ env.SWAGGER_UI_VERSION }}.tar.gz - tar -xzf /tmp/swagger.tar.gz -C /tmp/ - cp -r /tmp/swagger-ui-${{ env.SWAGGER_UI_VERSION }}/dist/* ./www/api-docs/ - cp pfSense-pkg-RESTAPI/files/usr/local/www/api/swagger/index.css ./www/api-docs/index.css - cp openapi.json/openapi.json ./www/api-docs/openapi.json - echo 'window.onload = function() { - document.title = "pfSense REST API Documentation"; - window.ui = SwaggerUIBundle({ - url: "/api-docs/openapi.json", - dom_id: "#swagger-ui", - deepLinking: true, - presets: [ - SwaggerUIBundle.presets.apis, - SwaggerUIStandalonePreset - ], - plugins: [ - SwaggerUIBundle.plugins.DownloadUrl - ], - layout: "StandaloneLayout", - supportedSubmitMethods: [], - validatorUrl: null - }); - };' > ./www/api-docs/swagger-initializer.js - - - name: Write GraphQL schema - run: | - mkdir -p ./www/graphql-docs/ - cp schema.graphql/schema.graphql ./www/graphql-docs/schema.graphql - - - name: Build PHP reference documentation - run: | - mkdir ./www/php-docs - wget https://phpdoc.org/phpDocumentor.phar - chmod +x phpDocumentor.phar - ./phpDocumentor.phar - mv ./.phpdoc/build/* ./www/php-docs/ - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3.0.1 - with: - path: "./www" - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4.0.5 diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/AvailablePackageCache.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/AvailablePackageCache.inc index 37e2bc0e..eb9a7801 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/AvailablePackageCache.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/AvailablePackageCache.inc @@ -12,8 +12,7 @@ use function RESTAPI\Dispatchers\get_pkg_info; */ class AvailablePackageCache extends Cache { public int $timeout = 120; - public string $schedule = 'hourly'; - + public string $schedule = '12 * * * *'; # Run at irregular interval to avoid conflicts with repo jobs /** * Retrieves the available package information to cache from external repos */ diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/RESTAPIVersionReleasesCache.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/RESTAPIVersionReleasesCache.inc index 6db84fb2..74f00be6 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/RESTAPIVersionReleasesCache.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/RESTAPIVersionReleasesCache.inc @@ -15,7 +15,7 @@ class RESTAPIVersionReleasesCache extends Cache { const RELEASES_URL = 'https://api.github.com/repos/jaredhendrickson13/pfsense-api/releases'; public int $timeout = 30; - public string $schedule = 'hourly'; + public string $schedule = '0 * * * *'; /** * Retrieves available release information from external repos and updates the releases cache files. diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Dispatcher.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Dispatcher.inc index 78e708d2..e3afd073 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Dispatcher.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Dispatcher.inc @@ -19,10 +19,6 @@ class Dispatcher { * @const DISPATCH_SCRIPT The absolute file path to the dispatch.sh helper script. */ const DISPATCH_SCRIPT = '/usr/local/pkg/RESTAPI/.resources/scripts/dispatch.sh'; - /** - * @const SCHEDULE_OPTIONS The cron event schedules supported by Dispatchers. - */ - const SCHEDULE_OPTIONS = ['hourly', 'daily', 'weekly']; /** * @var string $full_name @@ -297,27 +293,27 @@ class Dispatcher { # Only proceed if a schedule was requested if ($this->schedule) { # Ensure the requested schedule is supported - if (!in_array($this->schedule, self::SCHEDULE_OPTIONS)) { + if (count(explode(' ', $this->schedule)) !== 5) { throw new ServerError( message: "Dispatcher schedule `$this->schedule` is not a supported schedule frequency.", response_id: 'DISPATCHER_SCHEDULE_UNSUPPORTED', ); } - # Check if a cron job already exists for this dispatcher - $dispatcher_cron_job_q = CronJob::query(command: $this->schedule_command); - - # Delete the cron job for this dispatcher if it exists, so we can recreate it with current values - if ($dispatcher_cron_job_q->exists()) { - $existing_cron_job = $dispatcher_cron_job_q->first(); - $existing_cron_job->packages = []; // Don't require the pfSense-pkg-Cron package to delete - $existing_cron_job->delete(); - } + # Remove any existing scheduled CronJob for this Dispatcher + $this->remove_schedule(); # Create the cron job for this dispatcher + $cron_expr = explode(' ', $this->schedule); $cron_job = new CronJob( - data: ['minute' => "@$this->schedule", 'who' => 'root', 'command' => $this->schedule_command], require_pkg: false, + minute: $cron_expr[0], + hour: $cron_expr[1], + mday: $cron_expr[2], + month: $cron_expr[3], + wday: $cron_expr[4], + who: 'root', + command: $this->schedule_command, ); $cron_job->create(); @@ -330,4 +326,19 @@ class Dispatcher { return null; } + + /** + * Removes the scheduled CronJob for this Dispatcher if it exists. + */ + public function remove_schedule(): void { + # Check if a cron job already exists for this dispatcher + $dispatcher_cron_job_q = CronJob::query(command: $this->schedule_command); + + # Delete the cron job for this dispatcher if it exists, so we can recreate it with current values + if ($dispatcher_cron_job_q->exists()) { + $existing_cron_job = $dispatcher_cron_job_q->first(); + $existing_cron_job->packages = []; // Don't require the pfSense-pkg-Cron package to delete + $existing_cron_job->delete(); + } + } } diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackendServer.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackendServer.inc index 52d616e4..3e70884a 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackendServer.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackendServer.inc @@ -23,6 +23,7 @@ class HAProxyBackendServer extends Model { public BooleanField $ssl; public BooleanField $sslserververify; public IntegerField $serverid; + public StringField $advanced; public function __construct(mixed $id = null, mixed $parent_id = null, mixed $data = [], ...$options) { # Set model attributes @@ -80,6 +81,12 @@ class HAProxyBackendServer extends Model { help_text: 'The unique ID for this backend server. This value is set by the system for internal use and ' . 'cannot be changed.', ); + $this->advanced = new StringField( + default: '', + allow_empty: true, + help_text: 'Allows adding custom HAProxy server settings to the server.', + ); + parent::__construct($id, $parent_id, $data, ...$options); } diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc index 721d6c95..11a213ae 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc @@ -51,6 +51,12 @@ class WireGuardTunnel extends Model { indicates_false: 'no', help_text: 'Enables or disables this tunnels and any associated peers.', ); + $this->descr = new StringField( + required: false, + default: '', + allow_empty: true, + help_text: 'A description for this WireGuard tunnel.', + ); $this->listenport = new PortField( unique: true, default: '51820', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc index 148067f5..ceddaf44 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc @@ -134,10 +134,14 @@ class APICoreDispatcherTestCase extends TestCase { $this->assert_equals($dispatcher->setup_schedule(), null); # Assign the dispatcher a schedule and ensure setup_schedule() returns a CronJob object with the correct schedule - $dispatcher->schedule = 'daily'; + $dispatcher->schedule = '1 2 3 4 5'; $dispatcher_cron_job = $dispatcher->setup_schedule(); $cron_job_cmd = '/usr/local/pkg/RESTAPI/.resources/scripts/manage.php notifydispatcher Dispatcher'; - $this->assert_equals($dispatcher_cron_job->minute->value, "@$dispatcher->schedule"); + $this->assert_equals($dispatcher_cron_job->minute->value, '1'); + $this->assert_equals($dispatcher_cron_job->hour->value, '2'); + $this->assert_equals($dispatcher_cron_job->mday->value, '3'); + $this->assert_equals($dispatcher_cron_job->month->value, '4'); + $this->assert_equals($dispatcher_cron_job->wday->value, '5'); $this->assert_equals($dispatcher_cron_job->command->value, $cron_job_cmd); # Delete the CronJob diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardTunnelTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardTunnelTestCase.inc index c7a39185..3a2f5cb4 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardTunnelTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardTunnelTestCase.inc @@ -124,6 +124,7 @@ class APIModelsWireGuardTunnelTestCase extends TestCase { $tunnel = new WireGuardTunnel( privatekey: 'KG0BA4UyPilHH5qnXCfr6Lw8ynecOPor88tljLy3AHk=', listenport: '55000', + descr: 'test', async: false, ); $tunnel->create(apply: true); @@ -134,6 +135,7 @@ class APIModelsWireGuardTunnelTestCase extends TestCase { $this->assert_str_contains($wg_showconf->output, 'ListenPort = ' . $tunnel->listenport->value); $this->assert_str_contains($wg_showconf->output, 'PrivateKey = ' . $tunnel->privatekey->value); $this->assert_str_contains($wg_show->output, 'public key: ' . $tunnel->publickey->value); + $this->assert_equals($tunnel->descr->value, 'test'); # Update the tunnel with new values $tunnel->from_representation(privatekey: 'GNdQw+ujEIVgys4B2dDCXcBpiiQsNd2bAq5hnTp+smg=', listenport: '51820');