Smallweb 0.13 - Private Apps, Terminal, WebDAV, and CLI entrypoints

by Achille Lacoin

6 min read

Hey, It's been a while since the last release of smallweb! I've been taking a break for the month of August, but I'm back with the biggest smallweb release yet, including:

  • Protecting private apps with authentication
  • Built-in terminal and WebDAV services
  • CLI entrypoints

Private Apps

You can now easily guard your smallweb apps behind an authentication mechanism.

  1. Add the some field to the global config (located at ~/.config/smallweb/config.json)

    {
        "email": "[email protected]"
    }
    

    You can easily generate a new token with the smallweb token command.

  2. Set the private field to true in your app config.

    // ~/smallweb/example-app/smallweb.json
    {
        "private": true
    }
    

From now on, you will need to authenticate when accessing the app.

alt text

The auth mechanism leverage lastlogin.io to provide a simple and secure authentication mechanism. I might host my own instance of lastlogin in the future, but for now, I choosed to rely on the public instance.

Alternatively, you can provide a token in the Authorization header.

curl https://example-app.localhost --header "Authorization: Bearer <token>"

Or alternatively:

curl https://<token>@example-app.localhost

To get a smallweb token, you can use the smallweb token create command.

$ smallweb token create --description "My secret token"
Pj0e8hlwTolo7IoPo6ksA

Built-in Admin Apps

This new authentication mechanism enabled me to distribute admin services as part of smallweb. In this release, there are two admin apps available:

  • webdav.<your-domain>: A WebDAV server to manage your files
  • cli.<your-domain>: Allowing you to access the cli from your browser

But i'm considering adding more in the future (feel free to suggest some!).

cli service

You can now access the cli from your browser! This is a great way to manage a remote smallweb instance without having to ssh into it.

If you go to cli.<your-domain>, you will be prompted to authenticate, then be able to run any command you want.

Smallweb CLI demo

The cli is protected behind the same authentication mechanism as the rest of the apps.

smallweb ls --json -> curl -X POST 'https://cli.<your-domain>/ls?json' --header "Authorization: Bearer my-secret-token"

In a future release, the cli service might include a terminal emulator using xterm.js.

webdav service

I've been rediscovering the power of WebDAV recently, and I wanted to make it easy to pair it with smallweb. The built-in webdav servers allows you to manage/backup your smallweb folder using any webdav client.

It means that smallweb can now be integrated with:

To authenticate, you can either:

  • Use bearer token authentication
  • Use basic authentication, and provide the token as the username

Currently the WebDAV server does not comes with any UI, as I was not able to find a webdav client both easily embeddable and good-looking. If you have any suggestion, feel free to reach out!

Having a webdav server also allows you to easily backup your smallweb apps using a tool like rclone.

Smallweb WebDAV demo

CLI entrypoints

Smallweb apps are required to export an object, as their default export, that contain's a fetch method to handle incoming requests.

// ~/smallweb/example-app/main.ts
export default {
    fetch: async (request: Request) => {
        return new Response('Hello, World!');
    }
}

From now on, you can also register a run method to handle cli commands.

// ~/smallweb/example-app/main.ts
export default {
    fetch: async (request: Request) => {
        return new Response('Hello, World!');
    },
    run: async (args: string[]) => {
        console.log(`Hello, ${args[0]}!`);
    }
}

Once you have registered a run method, you can run it with the smallweb run command.

$ smallweb run example-app pomdtr
Hello, pomdtr!

Smallweb commands run with the same permissions as the app itself. You can access stdin/stdout/stderr, not spawn subprocesses.

You can also access the newly created cli from the internal cli.<your-domain> service, either from your browser or using curl.

curl -X POST https://cli.<your-domain>/run/example-app/pomdtr -H "Authorization: Bearer my-secret-token"

These cli entrypoints are also leveraged for cron tasks (which schema has been updated in this release).

// ~/smallweb/example-app/smallweb.json
{
    "crons": [
        {
            "name": "say-hello-to-pomdtr",
            "description": "Say hello to pomdtr every day",
            "schedule": "0 0 * * *",
            "args": ["pomdtr"]
        }
    ]
}

Updated permissions

You used to be able to customize the permissions of a smallweb app using the permissions field in the app config. There was a big issue with this approach: if an app had write access to the filesystem, it could edit it's own permissions, and escalate its privileges.

Since I envision smallweb as a platform where user can run remote or AI generated code, I wanted to make sure that the permissions of an app are as restricted as possible.

In this release, app permissions are now longer customizable. Instead, each smallweb app is allowed to:

  • read and write in its own directory
  • access the network using the fetch API
  • access the env variable defined either in the global config or in the .env file

If you want to give you app additional permissions, you can provide it with a token has an environment variable.

# ~/smallweb/example-app.env
SMALLWEB_TOKEN=my-secret-token

Then, you'll be able to access the cli from your app:

const token = Deno.env.get("SMALLWEB_TOKEN");

const resp = await fetch("https://cli.<your-domain>/ls?json", {
    headers: {
        method: "POST",
        Authorization: `Bearer ${token}`,
    }
});

const apps = await resp.json();

Or build your own smallweb editor using a WebDAV client!

smallweb edit command

Use the smallweb edit [app] command to open an app in your default editor.

Of course, it integrates with the cli.<your-domain> service, allowing you to edit your apps from your browser.

smallweb types command

With the addition of the run method, it can become a bit cumbersome to check that your default export is compatible with the expected smallweb schema.

I added a new smallweb types command to help you generate types hint.

To add the smallweb types to your project, run:

smallweb types > smallweb.d.ts

You can then reference them in your project:

export default {
    fetch: async (request) => {
        return new Response('Hello, World!');
    },
    run: async (args) => {
        console.log(`Hello, ${args[0]}!`);
    }
} satisfies Smallweb.App;

Simplified Global Configuration

The global configuration now has the following schema:

// ~/.config/smallweb/config.json
{
    "host": "127.0.0.1",
    "port": 8080,
    "email": "[email protected]",
    "domain": "smallweb.run",
    "dir": "~/smallweb"
}

All of these fields can also be provided using environment variables (ex: host can be provided using the SMALLWEB_HOST environment variable).

As you can see, the complex domain rooting from previous versions has been dropped. A smallweb instance maps a single wildcard domain to a single directory. If you want to manage multiple domains, feel free to run multiple instances of smallweb!

ex: I host two instances of smallweb on my hetzner VM (smallweb.run and pomdtr.me). To do so, I created two users (smallweb.run and pomdtr.me), and run smallweb as a service for each of them.

Of course, you can still attach a custom domain to a specific app by adding a CNAME file in the app directory, containing the domain you want to attach.

Updated app config

The schema of the smallweb config was updated:

{
    "private": true,
    "entrypoint": "main.ts",
    "root": "public",
    "crons": []
}

We already detailed above:

  • The addition of the private field
  • The removal of the permissions field
  • The update of the crons field

In addition to this:

  • the serve field was replaced by the entrypoint field. You can use it to specify which file exports the app object. If you don't provide it, smallweb will default to main.[js,ts,jsx,tsx].
  • the root field allows you to specify the root directory of the app. The entrypoint file will be resolved relative to this directory.

Smallweb no longer serve static files by default. If you want to serve static files, you can use the following main.ts:

// ~/smallweb/example-app/main.ts
import { serveDir } from "jsr:@std/http/file-server";

export default {
  fetch: (req: Request) => serveDir(req, {
    // The root directory of the app
    fsRoot: "./static",
  }),
}

Or even simpler, add the following smallweb.json file:

{
    "entrypoint": "jsr:@smallweb/file-server",
    "root": "static" // or omit it to serve the whole app directory
}