diff --git a/cmd/radioctl/radioctl.go b/cmd/radioctl/radioctl.go
index 263b1a308c2fb633a24472609052c5ce9a7e8860..7d7c82ed2b970df3bed388abbce2627b5ee455f7 100644
--- a/cmd/radioctl/radioctl.go
+++ b/cmd/radioctl/radioctl.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"context"
 	"encoding/json"
 	"flag"
 	"fmt"
@@ -9,14 +10,21 @@ import (
 	"os"
 	"strconv"
 	"strings"
+	"time"
 
 	"git.autistici.org/ale/autoradio"
+	"git.autistici.org/ale/autoradio/client"
+	pb "git.autistici.org/ale/autoradio/proto"
 	"github.com/gonuts/commander"
 	gonutsflag "github.com/gonuts/flag"
+	"go.etcd.io/etcd/clientv3"
 )
 
 // Format for output of structured data.
-var outputFormat = flag.String("format", "txt", "Output format for structured data (json, txt)")
+var (
+	etcdEndpoints = flag.String("etcd", "http://localhost:2379", "Etcd endpoints (comma-separated list of URLs)")
+	outputFormat  = flag.String("format", "txt", "Output format for structured data (json, txt)")
+)
 
 type optionalValue struct {
 	isSet bool
@@ -135,15 +143,27 @@ func (b *BaseCommand) Command() *commander.Command {
 func (b *BaseCommand) Run(args []string) {
 }
 
-func getClient() *autoradio.Client {
-	return autoradio.NewClient(autoradio.NewEtcdClient(false))
+var auClient *client.Client
+
+func getClient() *client.Client {
+	if auClient == nil {
+		cli, err := clientv3.New(clientv3.Config{
+			Endpoints:   strings.Split(*etcdEndpoints, ","),
+			DialTimeout: 5 * time.Second,
+		})
+		if err != nil {
+			log.Fatalf("failed to connect to etcd: %v", err)
+		}
+		auClient = client.New(cli)
+	}
+	return auClient
 }
 
-func setRelay(m *autoradio.Mount, relayUrl string) {
+func setRelay(m *pb.Mount, relayUrl string) {
 	if relayUrl == "" {
 		// Randomly generate source credentials.
-		m.Username = autoradio.GenerateUsername(m.Name)
-		m.Password = autoradio.GeneratePassword()
+		m.SourceUsername = autoradio.GenerateUsername(m.Path)
+		m.SourcePassword = autoradio.GeneratePassword()
 	} else {
 		// Validate the given relay URL.
 		u, err := url.Parse(relayUrl)
@@ -151,30 +171,30 @@ func setRelay(m *autoradio.Mount, relayUrl string) {
 			log.Fatal(err)
 		}
 		m.RelayUrl = u.String()
-		m.Username = ""
-		m.Password = ""
+		m.SourceUsername = ""
+		m.SourcePassword = ""
 	}
 }
 
-func printMount(m *autoradio.Mount) {
+func printMount(m *pb.Mount) {
 	switch *outputFormat {
 	case "json":
 		s, _ := json.MarshalIndent(m, "", "    ")
 		os.Stdout.Write(s)
 
 	case "txt":
-		fmt.Printf("path=%s\n", m.Name)
-		if m.Username != "" {
-			fmt.Printf("username=%s\npassword=%s\n", m.Username, m.Password)
+		fmt.Printf("path=%s\n", m.Path)
+		if m.SourceUsername != "" {
+			fmt.Printf("username=%s\npassword=%s\n", m.SourceUsername, m.SourcePassword)
 		}
 		if m.RelayUrl != "" {
 			fmt.Printf("relay_url=%s\n", m.RelayUrl)
 		}
-		if m.Fallback != "" {
-			fmt.Printf("fallback_url=%s\n", m.Fallback)
+		if m.FallbackPath != "" {
+			fmt.Printf("fallback_url=%s\n", m.FallbackPath)
 		}
-		if t := m.Transcoding; t != nil {
-			fmt.Printf("transcode_source_url=%s\n", t.SourceName)
+		if t := m.TranscodeParams; t != nil {
+			fmt.Printf("transcode_source_url=%s\n", t.SourcePath)
 			fmt.Printf("transcode_format=%s\n", t.Format)
 			fmt.Printf("transcode_bitrate=%d\n", t.BitRate)
 			fmt.Printf("transcode_quality=%f\n", t.Quality)
@@ -186,8 +206,8 @@ func printMount(m *autoradio.Mount) {
 	}
 }
 
-func mountExists(name string, client *autoradio.Client) bool {
-	m, _ := client.GetMount(name)
+func mountExists(name string, c *client.Client) bool {
+	m, _ := c.GetMount(context.Background(), name)
 	return m != nil
 }
 
@@ -229,22 +249,22 @@ func (cmd *createMountCommand) Run(args []string) {
 	}
 
 	// Check if the mount already exists.
-	client := getClient()
-	if mountExists(path, client) {
+	c := getClient()
+	if mountExists(path, c) {
 		log.Fatal("ERROR: A mount with that name already exists!")
 	}
 
 	// Create the new mount and set the relevant fields (depending
 	// on the options passed to the command).
-	m := &autoradio.Mount{Name: path}
+	m := &pb.Mount{Path: path}
 	setRelay(m, cmd.relay)
-	m.Fallback = cmd.fallback
+	m.FallbackPath = cmd.fallback
 
 	if err := m.Valid(); err != nil {
 		log.Fatalf("ERROR: mount configuration is invalid: %v", err)
 	}
 
-	if err := client.SetMount(m); err != nil {
+	if err := c.SetMount(context.Background(), m); err != nil {
 		log.Fatalf("ERROR: creating mount: %v", err)
 	}
 
@@ -254,8 +274,15 @@ func (cmd *createMountCommand) Run(args []string) {
 // Create a submount (transcoded stream).
 type createTranscodingMountCommand struct {
 	BaseCommand
-	params   *autoradio.EncodingParams
-	fallback string
+
+	sourcePath string
+	format     string
+	quality    float64
+	bitRate    int
+	sampleRate int
+	channels   int
+	stereoMode string
+	fallback   string
 }
 
 func newCreateTranscodingMountCommand() *createTranscodingMountCommand {
@@ -268,22 +295,17 @@ Create a new stream that will transcode the parent stream with
 different encoding parameters.
 `,
 		},
-		params: autoradio.NewEncodingParams(),
 	}
 }
 
-func addEncodingFlags(f *gonutsflag.FlagSet, p *autoradio.EncodingParams) {
-	f.StringVar(&p.SourceName, "source", "", "Source mountpoint")
-	f.StringVar(&p.Format, "codec", p.Format, "Encoding format")
-	f.Float64Var(&p.Quality, "quality", p.Quality, "Quality (for VBR encoders)")
-	f.IntVar(&p.BitRate, "bitrate", p.BitRate, "Bitrate (kbps)")
-	f.IntVar(&p.SampleRate, "samplerate", p.SampleRate, "Sample rate (Hz)")
-	f.IntVar(&p.Channels, "channels", p.Channels, "Number of channels")
-	f.StringVar(&p.StereoMode, "stereo-mode", p.StereoMode, "Stereo mode for mp3 codec (stereo, joint_stereo)")
-}
-
 func (cmd *createTranscodingMountCommand) AddFlags(f *gonutsflag.FlagSet) {
-	addEncodingFlags(f, cmd.params)
+	f.StringVar(&cmd.sourcePath, "source", "", "Source mountpoint")
+	f.StringVar(&cmd.format, "codec", "mp3", "Encoding format")
+	f.Float64Var(&cmd.quality, "quality", 0, "Quality (for VBR encoders)")
+	f.IntVar(&cmd.bitRate, "bitrate", 32, "Bitrate (kbps)")
+	f.IntVar(&cmd.sampleRate, "samplerate", 44100, "Sample rate (Hz)")
+	f.IntVar(&cmd.channels, "channels", 2, "Number of channels")
+	f.StringVar(&cmd.stereoMode, "stereo-mode", "joint_stereo", "Stereo mode for mp3 codec (stereo, joint_stereo)")
 	f.StringVar(&cmd.fallback, "fallback", "", "Fallback stream URL")
 }
 
@@ -299,28 +321,37 @@ func (cmd *createTranscodingMountCommand) Run(args []string) {
 	}
 
 	// The mount path should not exist.
-	client := getClient()
-	if mountExists(path, client) {
+	c := getClient()
+	if mountExists(path, c) {
 		log.Fatal("ERROR: a mount with that name already exists!")
 	}
 	// The source mount should exist.
-	if !mountExists(cmd.params.SourceName, client) {
+	if !mountExists(cmd.sourcePath, c) {
 		log.Fatal("ERROR: the source mount does not exist!")
 	}
 
 	// Retrieve the parent mount point and add a TranscodingMount.
-	m := &autoradio.Mount{
-		Name:        path,
-		Transcoding: cmd.params,
+	m := &pb.Mount{
+		Path:         path,
+		FallbackPath: cmd.fallback,
+		Transcode:    true,
+		TranscodeParams: &pb.EncodingParams{
+			SourcePath: cmd.sourcePath,
+			Format:     cmd.format,
+			BitRate:    int32(cmd.bitRate),
+			SampleRate: int32(cmd.sampleRate),
+			Channels:   int32(cmd.channels),
+			StereoMode: cmd.stereoMode,
+			Quality:    float32(cmd.quality),
+		},
 	}
 	setRelay(m, "")
-	m.Fallback = cmd.fallback
 
 	if err := m.Valid(); err != nil {
 		log.Fatalf("ERROR: mount configuration is invalid: %v", err)
 	}
 
-	if err := client.SetMount(m); err != nil {
+	if err := c.SetMount(context.Background(), m); err != nil {
 		log.Fatalf("ERROR: creating mount: %v", err)
 	}
 
@@ -402,8 +433,8 @@ func (cmd *editMountCommand) Run(args []string) {
 		log.Fatal("Wrong number of arguments")
 	}
 
-	client := getClient()
-	m, err := client.GetMount(args[0])
+	c := getClient()
+	m, err := c.GetMount(context.Background(), args[0])
 	if err != nil {
 		log.Fatalf("ERROR: %v", err)
 	}
@@ -413,43 +444,43 @@ func (cmd *editMountCommand) Run(args []string) {
 
 	// Only set those fields that were passed on the command line.
 	if cmd.fallback.IsSet() {
-		m.Fallback = cmd.fallback.Value()
+		m.FallbackPath = cmd.fallback.Value()
 	}
 	if cmd.relay.IsSet() {
 		setRelay(m, cmd.relay.Value())
 	}
 
-	if cmd.transcodingOptionsSet() && m.Transcoding == nil {
+	if cmd.transcodingOptionsSet() && !m.Transcode {
 		log.Fatal("ERROR: can't set transcoding options on a non-transcoding mount (delete and re-create)")
 	}
 
 	if cmd.transFormat.IsSet() {
-		m.Transcoding.Format = cmd.transFormat.Value()
+		m.TranscodeParams.Format = cmd.transFormat.Value()
 	}
 	if cmd.transSource.IsSet() {
-		m.Transcoding.SourceName = cmd.transSource.Value()
+		m.TranscodeParams.SourcePath = cmd.transSource.Value()
 	}
 	if cmd.transBitRate.IsSet() {
-		m.Transcoding.BitRate = cmd.transBitRate.Value()
+		m.TranscodeParams.BitRate = int32(cmd.transBitRate.Value())
 	}
 	if cmd.transSampleRate.IsSet() {
-		m.Transcoding.SampleRate = cmd.transSampleRate.Value()
+		m.TranscodeParams.SampleRate = int32(cmd.transSampleRate.Value())
 	}
 	if cmd.transQuality.IsSet() {
-		m.Transcoding.Quality = cmd.transQuality.Value()
+		m.TranscodeParams.Quality = float32(cmd.transQuality.Value())
 	}
 	if cmd.transChannels.IsSet() {
-		m.Transcoding.Channels = cmd.transChannels.Value()
+		m.TranscodeParams.Channels = int32(cmd.transChannels.Value())
 	}
 	if cmd.transStereoMode.IsSet() {
-		m.Transcoding.StereoMode = cmd.transStereoMode.Value()
+		m.TranscodeParams.StereoMode = cmd.transStereoMode.Value()
 	}
 
 	if err := m.Valid(); err != nil {
 		log.Fatalf("ERROR: mount configuration is invalid: %v", err)
 	}
 
-	if err := client.SetMount(m); err != nil {
+	if err := c.SetMount(context.Background(), m); err != nil {
 		log.Fatalf("ERROR: updating mount: %v", err)
 	}
 
@@ -478,25 +509,25 @@ func (cmd *deleteMountCommand) Run(args []string) {
 		log.Fatal("Wrong number of arguments")
 	}
 	path := args[0]
-	client := getClient()
-	if !mountExists(path, client) {
+	c := getClient()
+	if !mountExists(path, c) {
 		log.Fatal("ERROR: mount not found")
 	}
 
-	if err := client.DelMount(path); err != nil {
+	if err := c.DeleteMount(context.Background(), path); err != nil {
 		log.Fatalf("ERROR: deleting mount: %v", err)
 	}
 
 	// Delete all the transcoding mounts that have this as a
 	// source.
-	mounts, err := client.ListMounts()
+	mounts, err := c.ListMounts(context.Background())
 	if err != nil {
 		log.Fatalf("ERROR: %v", err)
 	}
 	for _, m := range mounts {
-		if m.HasTranscoder() && m.Transcoding.SourceName == path {
-			if err := client.DelMount(m.Name); err != nil {
-				log.Printf("ERROR: deleting transcoded mount %s: %v", m.Name, err)
+		if m.HasTranscoder() && m.TranscodeParams.SourcePath == path {
+			if err := c.DeleteMount(context.Background(), m.Path); err != nil {
+				log.Printf("ERROR: deleting transcoded mount %s: %v", m.Path, err)
 			}
 		}
 	}
@@ -526,13 +557,13 @@ func (cmd *listMountsCommand) Run(args []string) {
 		log.Fatal("Too many arguments")
 	}
 
-	mounts, err := getClient().ListMounts()
+	mounts, err := getClient().ListMounts(context.Background())
 	if err != nil {
 		log.Fatalf("ERROR: %v", err)
 	}
 	var names []string
 	for _, m := range mounts {
-		names = append(names, m.Name)
+		names = append(names, m.Path)
 	}
 
 	switch *outputFormat {
@@ -571,7 +602,7 @@ func (cmd *showMountCommand) Run(args []string) {
 		log.Fatal("Wrong number of arguments")
 	}
 
-	m, err := getClient().GetMount(args[0])
+	m, err := getClient().GetMount(context.Background(), args[0])
 	if err != nil {
 		log.Fatalf("ERROR: %v", err)
 	}
@@ -605,7 +636,7 @@ func (cmd *backupCommand) Run(args []string) {
 		log.Fatal("Too many arguments")
 	}
 
-	mounts, err := getClient().ListMounts()
+	mounts, err := getClient().ListMounts(context.Background())
 	if err != nil {
 		log.Fatalf("ERROR: %v", err)
 	}
@@ -636,15 +667,15 @@ func (cmd *restoreCommand) Run(args []string) {
 		log.Fatal("Too many arguments")
 	}
 
-	var mounts []*autoradio.Mount
+	var mounts []*pb.Mount
 	if err := json.NewDecoder(os.Stdin).Decode(&mounts); err != nil {
 		log.Fatalf("ERROR: %v", err)
 	}
 
-	client := getClient()
+	c := getClient()
 	for _, m := range mounts {
-		if err := client.SetMount(m); err != nil {
-			log.Printf("ERROR: creating mount %s: %v", m.Name, err)
+		if err := c.SetMount(context.Background(), m); err != nil {
+			log.Printf("ERROR: creating mount %s: %v", m.Path, err)
 		}
 	}
 }