-
- ckan-devstaller{" "}
-
-
-
- Launch CKAN dev instances within minutes.
-
-
- ckan-devstaller is a command-line tool to automate installing CKAN for
- development using ckan-compose on a new Ubuntu 22.04 instance.
-
-
- Provided by{" "}
-
- datHere
-
- .
-
-
-
- Get Started
-
-
- Source Code
-
-
-
- } href="/docs/builder" title="Quick start">
- Get started with ckan-devstaller and install CKAN within minutes
-
- } href="/docs/builder" title="Builder">
- Customize your installation with an interactive web GUI
-
- }
- href="/docs/reference/installation-architecture"
- title="Installation architecture"
- >
- Learn about where files are installed after running ckan-devstaller
-
- }
- href="https://github.com/dathere/ckan-devstaller"
- title="Source code"
- >
- View the source code of ckan-devstaller on GitHub
-
-
-
-
- One of the primary goals of ckan-devstaller is to ease
- installation of CKAN for development. Built with Rust for speed
- and streamlining installation with{" "}
-
- ckan-compose
-
- , ckan-devstaller improves installation speeds{" "}
- from hours/days to just minutes depending on your
- download speed.
-
-
-
- Get started
-
-
-
- ) : null}
- {active === 1 ? (
-
-
-
- Customize your installation with the Builder.
-
-
- Try out the interactive web GUI for customizing your CKAN
- installation. You can select:
-
-
-
Presets
-
CKAN version
-
Extensions
-
Features
-
-
- Then you can copy the provided ckan-devstaller command to run your
- selected configuration.
-
-
-
- Try out the Builder
-
-
-
- ) : null}
- {active === 2 ? (
-
-
-
- Designed for developers.
-
-
- We've kept development use cases in mind while developing
- ckan-devstaller, such as:
-
- After you've installed CKAN with ckan-devstaller, you can
- uninstall CKAN with ease. This allows for quickly re-installing
- CKAN for a different use case.
-
- {props.codeblockUninstall}
-
- Learn more about uninstalling
-
-
- ) : null}
-
-
-
- );
-}
diff --git a/docs/components/steps.tsx b/docs/components/steps.tsx
deleted file mode 100644
index ce348a2..0000000
--- a/docs/components/steps.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import type { ReactNode } from 'react';
-
-export function Steps({ children }: { children: ReactNode }) {
- return
{children}
;
-}
-
-export function Step({ children }: { children: ReactNode }) {
- return
{children}
;
-}
diff --git a/docs/components/tabs.tsx b/docs/components/tabs.tsx
deleted file mode 100644
index 54c41a3..0000000
--- a/docs/components/tabs.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import {
- type ComponentProps,
- createContext,
- type ReactNode,
- useContext,
- useEffect,
- useId,
- useMemo,
- useState,
-} from 'react';
-import { cn } from '../lib/cn';
-import * as Unstyled from './tabs.unstyled';
-
-type CollectionKey = string | symbol;
-
-export interface TabsProps
- extends Omit<
- ComponentProps,
- 'value' | 'onValueChange'
- > {
- /**
- * Use simple mode instead of advanced usage as documented in https://radix-ui.com/primitives/docs/components/tabs.
- */
- items?: string[];
-
- /**
- * Shortcut for `defaultValue` when `items` is provided.
- *
- * @defaultValue 0
- */
- defaultIndex?: number;
-
- /**
- * Additional label in tabs list when `items` is provided.
- */
- label?: ReactNode;
-}
-
-const TabsContext = createContext<{
- items?: string[];
- collection: CollectionKey[];
-} | null>(null);
-
-function useTabContext() {
- const ctx = useContext(TabsContext);
- if (!ctx) throw new Error('You must wrap your component in ');
- return ctx;
-}
-
-export const TabsList = React.forwardRef<
- React.ComponentRef,
- React.ComponentPropsWithoutRef
->((props, ref) => (
-
-));
-TabsList.displayName = 'TabsList';
-
-export const TabsTrigger = React.forwardRef<
- React.ComponentRef,
- React.ComponentPropsWithoutRef
->((props, ref) => (
-
-));
-TabsTrigger.displayName = 'TabsTrigger';
-
-export function Tabs({
- ref,
- className,
- items,
- label,
- defaultIndex = 0,
- defaultValue = items ? escapeValue(items[defaultIndex]) : undefined,
- ...props
-}: TabsProps) {
- const [value, setValue] = useState(defaultValue);
- const collection = useMemo(() => [], []);
-
- return (
- {
- if (items && !items.some((item) => escapeValue(item) === v)) return;
- setValue(v);
- }}
- {...props}
- >
- {items && (
-
- {label && (
- {label}
- )}
- {items.map((item) => (
-
- {item}
-
- ))}
-
- )}
- ({ items, collection }), [collection, items])}
- >
- {props.children}
-
-
- );
-}
-
-export interface TabProps
- extends Omit, 'value'> {
- /**
- * Value of tab, detect from index if unspecified.
- */
- value?: string;
-}
-
-export function Tab({ value, ...props }: TabProps) {
- const { items } = useTabContext();
- const resolved =
- value ??
- // eslint-disable-next-line react-hooks/rules-of-hooks -- `value` is not supposed to change
- items?.at(useCollectionIndex());
- if (!resolved)
- throw new Error(
- 'Failed to resolve tab `value`, please pass a `value` prop to the Tab component.',
- );
-
- return (
-
- {props.children}
-
- );
-}
-
-export function TabsContent({
- value,
- className,
- ...props
-}: ComponentProps) {
- return (
- figure:only-child]:-m-4 [&>figure:only-child]:border-none',
- className,
- )}
- {...props}
- >
- {props.children}
-
- );
-}
-
-/**
- * Inspired by Headless UI.
- *
- * Return the index of children, this is made possible by registering the order of render from children using React context.
- * This is supposed by work with pre-rendering & pure client-side rendering.
- */
-function useCollectionIndex() {
- const key = useId();
- const { collection } = useTabContext();
-
- useEffect(() => {
- return () => {
- const idx = collection.indexOf(key);
- if (idx !== -1) collection.splice(idx, 1);
- };
- }, [key, collection]);
-
- if (!collection.includes(key)) collection.push(key);
- return collection.indexOf(key);
-}
-
-/**
- * only escape whitespaces in values in simple mode
- */
-function escapeValue(v: string): string {
- return v.toLowerCase().replace(/\s/, '-');
-}
diff --git a/docs/components/tabs.unstyled.tsx b/docs/components/tabs.unstyled.tsx
deleted file mode 100644
index 783095f..0000000
--- a/docs/components/tabs.unstyled.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-'use client';
-
-import {
- type ComponentProps,
- createContext,
- useContext,
- useLayoutEffect,
- useMemo,
- useRef,
- useState,
-} from 'react';
-import * as Primitive from '@radix-ui/react-tabs';
-import { mergeRefs } from '../lib/merge-refs';
-import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
-
-type ChangeListener = (v: string) => void;
-const listeners = new Map();
-
-function addChangeListener(id: string, listener: ChangeListener): void {
- const list = listeners.get(id) ?? [];
- list.push(listener);
- listeners.set(id, list);
-}
-
-function removeChangeListener(id: string, listener: ChangeListener): void {
- const list = listeners.get(id) ?? [];
- listeners.set(
- id,
- list.filter((item) => item !== listener),
- );
-}
-
-export interface TabsProps extends ComponentProps {
- /**
- * Identifier for Sharing value of tabs
- */
- groupId?: string;
-
- /**
- * Enable persistent
- */
- persist?: boolean;
-
- /**
- * If true, updates the URL hash based on the tab's id
- */
- updateAnchor?: boolean;
-}
-
-const TabsContext = createContext<{
- valueToIdMap: Map;
-} | null>(null);
-
-function useTabContext() {
- const ctx = useContext(TabsContext);
- if (!ctx) throw new Error('You must wrap your component in ');
- return ctx;
-}
-
-export const TabsList = Primitive.TabsList;
-
-export const TabsTrigger = Primitive.TabsTrigger;
-
-/**
- * @internal You better not use it
- */
-export function Tabs({
- ref,
- groupId,
- persist = false,
- updateAnchor = false,
- defaultValue,
- value: _value,
- onValueChange: _onValueChange,
- ...props
-}: TabsProps) {
- const tabsRef = useRef(null);
- const [value, setValue] =
- _value === undefined
- ? // eslint-disable-next-line react-hooks/rules-of-hooks -- not supposed to change controlled/uncontrolled
- useState(defaultValue)
- : [_value, _onValueChange ?? (() => undefined)];
-
- const onChange = useEffectEvent((v: string) => setValue(v));
- const valueToIdMap = useMemo(() => new Map(), []);
-
- useLayoutEffect(() => {
- if (!groupId) return;
- const previous = persist
- ? localStorage.getItem(groupId)
- : sessionStorage.getItem(groupId);
-
- if (previous) onChange(previous);
- addChangeListener(groupId, onChange);
- return () => {
- removeChangeListener(groupId, onChange);
- };
- }, [groupId, persist]);
-
- useLayoutEffect(() => {
- const hash = window.location.hash.slice(1);
- if (!hash) return;
-
- for (const [value, id] of valueToIdMap.entries()) {
- if (id === hash) {
- onChange(value);
- tabsRef.current?.scrollIntoView();
- break;
- }
- }
- }, [valueToIdMap]);
-
- return (
- {
- if (updateAnchor) {
- const id = valueToIdMap.get(v);
-
- if (id) {
- window.history.replaceState(null, '', `#${id}`);
- }
- }
-
- if (groupId) {
- listeners.get(groupId)?.forEach((item) => {
- item(v);
- });
-
- if (persist) localStorage.setItem(groupId, v);
- else sessionStorage.setItem(groupId, v);
- } else {
- setValue(v);
- }
- }}
- {...props}
- >
- ({ valueToIdMap }), [valueToIdMap])}
- >
- {props.children}
-
-
- );
-}
-
-export function TabsContent({
- value,
- ...props
-}: ComponentProps) {
- const { valueToIdMap } = useTabContext();
-
- if (props.id) {
- valueToIdMap.set(value, props.id);
- }
-
- return (
-
- {props.children}
-
- );
-}
diff --git a/docs/components/ui/button.tsx b/docs/components/ui/button.tsx
deleted file mode 100644
index c614777..0000000
--- a/docs/components/ui/button.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { cva } from "class-variance-authority";
-
-const buttonVariants = cva(
- "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-fd-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring disabled:pointer-events-none disabled:opacity-50",
- {
- variants: {
- variant: {
- default:
- "bg-fd-background bg-gradient-to-b from-fd-primary to-fd-primary/60 text-fd-primary-foreground shadow-inner shadow-fd-background/20 hover:bg-fd-primary/90",
- outline: "border hover:bg-fd-accent hover:text-fd-accent-foreground",
- grow: "border bg-gradient-to-t from-fd-primary/10 shadow-inner shadow-fd-primary/10 hover:bg-fd-accent/50 hover:text-fd-accent-foreground",
- secondary:
- "border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent hover:text-fd-accent-foreground",
- ghost: "hover:bg-fd-accent hover:text-fd-accent-foreground",
- link: "text-fd-primary underline-offset-4 hover:underline",
- },
- size: {
- default: "h-10 px-4 py-2",
- icon: "p-1.5",
- sm: "h-9 px-3",
- lg: "h-11 px-6",
- xs: "px-2 py-1.5 text-xs",
- "icon-xs": "p-1 [&_svg]:size-4"
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- },
-);
-
-export { buttonVariants };
diff --git a/docs/components/ui/collapsible.tsx b/docs/components/ui/collapsible.tsx
deleted file mode 100644
index dbcf3f0..0000000
--- a/docs/components/ui/collapsible.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-'use client';
-import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
-import { forwardRef, useEffect, useState } from 'react';
-import { cn } from '../../lib/cn';
-
-const Collapsible = CollapsiblePrimitive.Root;
-
-const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
-
-const CollapsibleContent = forwardRef<
- HTMLDivElement,
- React.ComponentPropsWithoutRef
->(({ children, ...props }, ref) => {
- const [mounted, setMounted] = useState(false);
-
- useEffect(() => {
- setMounted(true);
- }, []);
-
- return (
-
- {children}
-
- );
-});
-
-CollapsibleContent.displayName =
- CollapsiblePrimitive.CollapsibleContent.displayName;
-
-export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/docs/components/ui/label.tsx b/docs/components/ui/label.tsx
deleted file mode 100644
index 5341821..0000000
--- a/docs/components/ui/label.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as LabelPrimitive from "@radix-ui/react-label"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-
-const labelVariants = cva(
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
-)
-
-const Label = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef &
- VariantProps
->(({ className, ...props }, ref) => (
-
-))
-Label.displayName = LabelPrimitive.Root.displayName
-
-export { Label }
diff --git a/docs/components/ui/sonner.tsx b/docs/components/ui/sonner.tsx
deleted file mode 100644
index 331836d..0000000
--- a/docs/components/ui/sonner.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-"use client";
-
-import {
- CircleCheckIcon,
- InfoIcon,
- Loader2Icon,
- OctagonXIcon,
- TriangleAlertIcon,
-} from "lucide-react";
-import { useTheme } from "next-themes";
-import { Toaster as Sonner, ToasterProps } from "sonner";
-
-const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme();
-
- return (
- ,
- info: ,
- warning: ,
- error: ,
- loading: ,
- }}
- style={
- {
- "--normal-bg": "var(--popover)",
- "--normal-text": "var(--popover-foreground)",
- "--normal-border": "var(--border)",
- "--border-radius": "var(--radius)",
- } as React.CSSProperties
- }
- {...props}
- />
- );
-};
-
-export { Toaster };
diff --git a/docs/components/ui/switch.tsx b/docs/components/ui/switch.tsx
deleted file mode 100644
index 9511e19..0000000
--- a/docs/components/ui/switch.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as SwitchPrimitives from "@radix-ui/react-switch"
-
-import { cn } from "@/lib/utils"
-
-const Switch = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-))
-Switch.displayName = SwitchPrimitives.Root.displayName
-
-export { Switch }
diff --git a/docs/content/docs/builder.mdx b/docs/content/docs/builder.mdx
deleted file mode 100644
index 41701ea..0000000
--- a/docs/content/docs/builder.mdx
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: Builder
-description: Customize your CKAN installation before running ckan-devstaller.
-icon: Blocks
----
-
-ckan-devstaller attempts to install a CKAN instance from source along with [ckan-compose](https://github.com/tino097/ckan-compose/tree/ckan-devstaller) and other optional features, intended for development use in a new Ubuntu 22.04 instance.
-
-Make sure `ckan-devstaller` is run in a **new** Ubuntu 22.04 instance. Do NOT run `ckan-devstaller` in an existing instance that is important for your usage.
-
-import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
-
-
- If you are using Ubuntu 22.04 on VirtualBox, you may need to add your user to the sudoers file before running the ckan-devstaller install script. Open a terminal in your virtual machine (VM), run `su -` and log in as the root user with the password you used to set up the VM, then type:
`sudo adduser sudo`
where `` is your username then restart your VM and run the ckan-devstaller installer script.
- Currently `ckan-devstaller` supports `x86_64` architecture. `ARM64` support is planned.
-
-
----
-
-import Builder from "@/components/builder";
-
-
diff --git a/docs/content/docs/changelog/0.3.0.mdx b/docs/content/docs/changelog/0.3.0.mdx
deleted file mode 100644
index b8143a7..0000000
--- a/docs/content/docs/changelog/0.3.0.mdx
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: Changelog for ckan-devstaller v0.3.0 (2025-10-14)
----
-
-Since v0.2.1 of ckan-devstaller, there have been many new features and changes now available in v0.3.0.
-
-## New web app: ckan-devstaller.dathere.com
-
-We've released a new web app [ckan-devstaller.dathere.com](https://ckan-devstaller.dathere.com) as the primary documentation site for ckan-devstaller.
-
-## Builder page
-
-There is now an interactive web GUI, the [Builder](/docs/builder), for users to customize their CKAN installation before copying the (now updated) ckan-devstaller command and running it on their terminal. This helps resolve issue [#6](https://github.com/dathere/ckan-devstaller/issues/11).
-
-## Updated Quick Start page
-
-The [Quick Start](/docs) page now includes three options for suggested installation methods:
-
-1. Use the interactive [Builder](/docs/builder).
-2. Install the "CKAN-only" preset with a script which installs the latest stable version of CKAN and ckan-compose (with optional non-interactive script).
-3. Install the "datHere default" preset with a script which installs the latest stable version of CKAN and ckan-compose (with optional non-interactive script) along with the DataStore, ckanext-scheming, and DataPusher+ extensions and also installs the `openssh-server` package.
-
-## Installation architecture page
-
-There is now an [Installation Architecture](/docs/reference/installation-architecture) page in the Reference section of the web app that provides a visual representation of where `ckan-devstaller` installs relevant files/folders.
-
-## Uninstall CKAN page
-
-There is now an [Uninstall CKAN](/docs/tutorials/uninstall-ckan) page in the Tutorials section of the web app that helps users understand how to uninstall their newly installed CKAN installation. This includes the option to either use the new `ckan-devstaller uninstall` subcommand or run the script directly.
-
-## README update
-
-The README on the [ckan-devstaller GitHub repository](https://github.com/dathere/ckan-devstaller) has been updated to have a more user-friendly focus for users and developers that may be new to CKAN thanks to the suggestions by [@drw](https://github.com/drw) in issues [#10](https://github.com/dathere/ckan-devstaller/issues/10) and [#11](https://github.com/dathere/ckan-devstaller/issues/11).
-
-## Changelog section
-
-We've added a Changelog section to the web app to denote new changes to `ckan-devstaller` for each release.
diff --git a/docs/content/docs/changelog/0.3.1.mdx b/docs/content/docs/changelog/0.3.1.mdx
deleted file mode 100644
index 536bde0..0000000
--- a/docs/content/docs/changelog/0.3.1.mdx
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: Changelog for ckan-devstaller v0.3.1 (2025-10-30)
----
-
-## Updated CKAN default versions to latest stable versions
-
-We have updated ckan-devstaller to suggest the recent releases of CKAN [2.11.4](https://docs.ckan.org/en/2.11/changelog.html#v-2-11-4-2025-10-29) and [2.10.9](https://docs.ckan.org/en/2.10/changelog.html#v-2-10-9-2025-10-29).
-
-## Added the "Developing with WSL" page in the Reference section
-
-Developers using Windows may benefit from the new [Developing with WSL](/docs/reference/developing-with-wsl) page in the Reference section to try their builds of ckan-devstaller and verify things work on a new install of Ubuntu 22.04.
-
-Alternatively if not trying to reset an entire WSL environment, developers can look into [`ckan-devstaller uninstall`](/docs/tutorials/uninstall-ckan).
-
-## Added install script switch to Builder page
-
-There is now a switch on the Builder page that is enabled by default for including the installation script for ckan-devstaller before running it. This is necessary for users that don't have ckan-devstaller installed yet and are using the Builder over the Quick Start scripts.
-
-## Default start with the Builder page
-
-We've changed "Get Started" links (and similar links) to the Builder page instead of the Quick Start page to users can quickly access the Builder again and get started using ckan-devstaller.
-
-## Update ckan-compose links to use ckan-devstaller branch
-
-We've updated ckan-compose links to use [the ckan-devstaller branch](https://github.com/tino097/ckan-compose/tree/ckan-devstaller).
-
-## Update VirtualBox notice with better formatting
-
-To ensure users using VirtualBox see the full command for adding a user to the sudoers file, we've improved the formatting of the note.
-
-## GitHub Action to verify CKAN install runs
-
-We've added a GitHub Action to run on push to the main branch to verify that a CKAN-only install runs. This doesn't include verbose testing but rather ensuring that the ckan-devstaller finishes without errors.
-
-## New --skip-run flag
-
-We've added a `--skip-run` flag to skip running CKAN at the end of installation.
diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx
deleted file mode 100644
index ffa0d8d..0000000
--- a/docs/content/docs/index.mdx
+++ /dev/null
@@ -1,120 +0,0 @@
----
-title: Quick Start
-description: Get started with ckan-devstaller and install CKAN within minutes.
-icon: Zap
----
-
-ckan-devstaller attempts to install a CKAN instance from source along with [ckan-compose](https://github.com/tino097/ckan-compose/tree/ckan-devstaller) and other optional features, intended for development use in a new Ubuntu 22.04 instance.
-
-Make sure `ckan-devstaller` is run in a **new** Ubuntu 22.04 instance. Do NOT run `ckan-devstaller` in an existing instance that is important for your usage.
-
-import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
-
-
- If you are using Ubuntu 22.04 on VirtualBox, you may need to add your user to the sudoers file before running the ckan-devstaller install script. Open a terminal in your virtual machine (VM), run `su -` and log in as the root user with the password you used to set up the VM, then type:
`sudo adduser sudo`
where `` is your username then restart your VM and run the ckan-devstaller installer script.
- Currently `ckan-devstaller` supports `x86_64` architecture. `ARM64` support is planned.
-
-
----
-
-## Install CKAN using ckan-devstaller
-
-You have several options to choose from for installation. Here are a few you may choose one from:
-
-import { Step, Steps } from 'fumadocs-ui/components/steps';
-
-
-
-
-### Customize your CKAN installation with the Builder (Recommended)
-
-}
- href="/docs/builder"
- title="Builder"
- >
- Click here to customize your CKAN installation with an interactive web GUI
-
-
-
-
-
-
-### Install the "CKAN-only" preset
-
-By running the following script, ckan-devstaller will be downloaded and the default configuration for installing CKAN with ckan-compose will be selected. You can then customize your configuration interactively in your terminal after running this script.
-
-```bash
-wget -O - https://github.com/dathere/ckan-devstaller/releases/download/0.3.1/install.bash | bash
-```
-
-If you'd rather skip the interactivity and go straight to installation, then run the following script instead:
-
-```bash
-wget -O - https://github.com/dathere/ckan-devstaller/releases/download/0.3.1/install.bash | bash -s skip-interactive
-```
-
-
-
-
-
-### Install the "datHere Default" preset
-
-The following script will download ckan-devstaller and select the following configuration:
-
-- CKAN latest stable version
-- [ckan-compose](https://github.com/tino097/ckan-compose/tree/ckan-devstaller)
-- [DataStore extension](https://docs.ckan.org/en/2.11/maintaining/datastore.html)
-- [ckanext-scheming extension](https://github.com/ckan/ckanext-scheming)
-- [DataPusher+ extension](https://github.com/dathere/datapusher-plus)
-- Install the `openssh-server` package for allowing SSH capability
-- [DRUF mode](https://github.com/dathere/datapusher-plus?tab=readme-ov-file#druf-dataset-resource-upload-first-workflow) for DataPusher+ is available but disabled by default.
-
-You can then customize your configuration interactively in your terminal after running this script.
-
-```bash
-wget -O - https://github.com/dathere/ckan-devstaller/releases/download/0.3.1/install.bash | bash -s dathere-default
-```
-
-If you'd rather skip the interactivity and go straight to installation, then run the following script instead:
-
-```bash
-wget -O - https://github.com/dathere/ckan-devstaller/releases/download/0.3.1/install.bash | bash -s dathere-default skip-interactive
-```
-
-
-
-
-## Learn more
-
-import { BlocksIcon, HomeIcon, GitMergeIcon, Trash2Icon } from 'lucide-react';
-
-
- }
- href="/docs/builder"
- title="Builder"
- >
- Customize your installation with an interactive web GUI
-
- }
- href="/docs/reference/installation-architecture"
- title="Installation architecture"
- >
- Learn about where files are installed after running ckan-devstaller
-
- }
- href="/docs/tutorials/uninstall-ckan"
- title="Uninstall CKAN"
- >
- Learn how to uninstall CKAN after running ckan-devstaller
-
- }
- href="https://github.com/dathere/ckan-devstaller"
- title="Source code">
- View the source code of ckan-devstaller on GitHub
-
-
diff --git a/docs/content/docs/meta.json b/docs/content/docs/meta.json
deleted file mode 100644
index 608d5ba..0000000
--- a/docs/content/docs/meta.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "pages": [
- "---Introduction---",
- "index",
- "builder",
- "---Further reading---",
- "tutorials",
- "reference",
- "changelog",
- "--- ---",
- "[Privacy Policy](https://dathere.com/privacy-policy/)"
- ]
-}
\ No newline at end of file
diff --git a/docs/content/docs/reference/developing-with-wsl.mdx b/docs/content/docs/reference/developing-with-wsl.mdx
deleted file mode 100644
index c413785..0000000
--- a/docs/content/docs/reference/developing-with-wsl.mdx
+++ /dev/null
@@ -1,131 +0,0 @@
----
-title: Developing with WSL
-description: Tips on how to develop ckan-devstaller on a Windows machine by leveraging Windows Subsystem for Linux.
----
-
-When developing ckan-devstaller on Windows, using Windows Subsystem for Linux (WSL) can be advantageous to demo an Ubuntu 22.04 environment without having to set up a virtual machine.
-
-import { Step, Steps } from 'fumadocs-ui/components/steps';
-import { File, Folder, Files } from 'fumadocs-ui/components/files';
-
-
-
-
-### Install the Ubuntu-22.04 distribution
-
-You'll need to have the Ubuntu-22.04 distribution installed by running the following command:
-
-```bash
-wsl --install Ubuntu-22.04 --version 2
-```
-
-
-
-
-
-### Export a base image
-
-
-
-Here's the expected set up where we create a WSL folder:
-
-```files
-/c/Users/rzmk/WSL
-├── images
-| ├── ubuntu-22-04-snapshot.tar
-├── instances
-| ├── cdr.vdhx
-```
-
-First we'll generate the `images/ubuntu-22-04-snapshot.tar` file:
-
-```bash
-wsl --export Ubuntu-22.04 ./images/ubuntu-22-04-snapshot.tar
-```
-
-
-
-### Generate a VDHX file for our new instance
-
-Next we'll generate the `instances/cdr.vdhx` file, so we can run the following:
-
-```bash
-wsl --import cdr ./instances ./images/ubuntu-22-04-snapshot.tar
-```
-
-
-
-
-
-### Access your new instance as root
-
-Now try to access your new Ubuntu 22.04 instance `cdr` as the `root` user by running:
-
-```bash
-wsl -d cdr
-```
-
-
-
-
-
-### Set yourself as the admin user on the instance and log in as the admin user
-
-Once logged in as `root`, you'll want to edit the `/etc/wsl.conf` file and modify it so you can login as an admin user instead of `root`. We can use the `nano` editor to modify the file:
-
-```bash
-nano /etc/wsl.conf
-```
-
-Modify the file by adding a `[user]` section with the value `default=rzmk` (where `rzmk` is your username). The file should look similar to this:
-
-```ini
-[boot]
-systemd=true
-
-[user]
-default=rzmk
-```
-
-Now leave your instance:
-
-```bash
-exit
-```
-
-Then terminate the instance:
-
-```bash
-wsl --terminate cdr
-```
-
-Run the instance again and you should be logged in as your admin user by default now:
-
-```bash
-wsl -d cdr
-```
-
-
-
-
-
-### Install ckan-devstaller
-
-Great, now you can go to the home directory and run one of the [quick start](/docs) scripts:
-
-```bash
-cd ~/
-```
-
-
-
-
-## Removing your instance
-
-When you want to remove your instance (e.g. so that you can start a brand new instance) then run the following to unregister it:
-
-```bash
-wsl --unregister cdr
-```
-
-Then you can follow the steps again from step 3 to generate and try out a new instance.
diff --git a/docs/content/docs/reference/installation-architecture.mdx b/docs/content/docs/reference/installation-architecture.mdx
deleted file mode 100644
index 16369f0..0000000
--- a/docs/content/docs/reference/installation-architecture.mdx
+++ /dev/null
@@ -1,76 +0,0 @@
----
-title: Installation architecture
-description: View a brief overview of what the installation from ckan-devstaller looks like
----
-
-import { File, Folder, Files } from 'fumadocs-ui/components/files';
-
-## CKAN and extensions
-
-The CKAN repository selected from ckan-devstaller is installed to `/usr/lib/ckan/default/src/ckan`. Extensions are also installed as sibling folders. For example if `ckanext-scheming` is also installed:
-
-```files
-/usr/lib/ckan/default/src
-├── ckan
-│ ├── ...
-├── ckanext-scheming
-│ ├── ...
-```
-
-The configuration file for CKAN is installed at `/etc/ckan/default/ckan.ini`:
-
-```files
-/etc/ckan/default
-├── ckan.ini
-├── who.ini
-```
-
-## ckan-compose
-
-We install certain first-time install files and [`ckan-compose`](https://github.com/tino097/ckan-compose/tree/ckan-devstaller) as a directory in the user's home (`~`) directory. For example for the user `adam`:
-
-```files
-/home/adam
-├── ahoy
-├── dpp_default_config.ini
-├── get-docker.sh
-├── permissions.sql
-├── ckan-compose
-│ ├── ...
-```
-
-After running ckan-devstaller you may also see many files starting with `qsv` and `README`. There are various files you can remove after running ckan-devstaller including:
-
-- `dpp_default_config.ini`
-- `get-docker.sh`
-- `permissions.sql`
-- `README`
-- The various `qsv` files
-
-Here's a script you can run for cleanup after running ckan-devstaller:
-
-```bash
-cd ~/
-rm -rf dpp_default_config.ini get-docker.sh permissions.sql README qsv*
-```
-
-## DataPusher+
-
-We install a compatible version of qsv with the DataPusher+ variant named `qsvdp` and move it to `/usr/local/bin`:
-
-```files
-/usr/local/bin
-├── qsvdp
-```
-
-The ckanext-scheming and DataPusher+ extensions are installed in the same location as other CKAN extensions:
-
-```files
-/usr/lib/ckan/default/src
-├── ckan
-| ├── ...
-├── ckanext-scheming
-| ├── ...
-├── datapusher_plus
-| ├── ...
-```
diff --git a/docs/content/docs/reference/meta.json b/docs/content/docs/reference/meta.json
deleted file mode 100644
index 0d08c26..0000000
--- a/docs/content/docs/reference/meta.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "pages": [
- "installation-architecture",
- "developing-with-wsl"
- ]
-}
\ No newline at end of file
diff --git a/docs/content/docs/tutorials/uninstall-ckan.mdx b/docs/content/docs/tutorials/uninstall-ckan.mdx
deleted file mode 100644
index fafe9fd..0000000
--- a/docs/content/docs/tutorials/uninstall-ckan.mdx
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: Uninstall CKAN
-description: How to uninstall CKAN after having installed with ckan-devstaller
----
-
-You may want to uninstall CKAN and related files after having ran ckan-devstaller. This can be useful if you want to re-run ckan-devstaller with a different configuration or are developing ckan-devstaller.
-
-The uninstallation process can be done by running:
-
-```bash
-./ckan-devstaller uninstall
-```
-
-The following script will be ran to uninstall CKAN and files related to ckan-devstaller:
-
-```bash
-sudo rm -rf /usr/lib/ckan
-sudo rm -rf /etc/ckan
-cd ~/
-rm -rf qsv*
-rm -rf README ckan-compose ahoy dpp_default_config.ini get-docker.sh permissions.sql
-```
diff --git a/docs/content/docs/what-is-ckan-devstaller.mdx b/docs/content/docs/what-is-ckan-devstaller.mdx
deleted file mode 100644
index 8cb135b..0000000
--- a/docs/content/docs/what-is-ckan-devstaller.mdx
+++ /dev/null
@@ -1,19 +0,0 @@
----
-title: What is ckan-devstaller?
-description: Learn about why ckan-devstaller was built and how it may help you.
-icon: CircleQuestionMark
----
-
-TODO: Improve this page.
-
-## Introduction
-
-Description
-
-Cards
-
-## Who is ckan-devstaller for
-
-## How can I use ckan-devstaller
-
-## Learn more
diff --git a/docs/lib/cn.ts b/docs/lib/cn.ts
deleted file mode 100644
index ba66fd2..0000000
--- a/docs/lib/cn.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { twMerge as cn } from 'tailwind-merge';
diff --git a/docs/lib/inter.ttf b/docs/lib/inter.ttf
deleted file mode 100644
index e31b51e..0000000
Binary files a/docs/lib/inter.ttf and /dev/null differ
diff --git a/docs/lib/layout.shared.tsx b/docs/lib/layout.shared.tsx
deleted file mode 100644
index ece1668..0000000
--- a/docs/lib/layout.shared.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
-import { SailboatIcon } from "lucide-react";
-
-/**
- * Shared layout configurations
- *
- * you can customise layouts individually from:
- * Home Layout: app/(home)/layout.tsx
- * Docs Layout: app/docs/layout.tsx
- */
-export function baseOptions(): BaseLayoutProps {
- return {
- nav: {
- title: (
- <>
-
- ckan-devstaller
- >
- ),
- },
- // see https://fumadocs.dev/docs/ui/navigation/links
- links: [],
- githubUrl: "https://github.com/dathere/ckan-devstaller",
- };
-}
diff --git a/docs/lib/merge-refs.ts b/docs/lib/merge-refs.ts
deleted file mode 100644
index 7d05f74..0000000
--- a/docs/lib/merge-refs.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type * as React from 'react';
-
-export function mergeRefs(
- ...refs: (React.Ref | undefined)[]
-): React.RefCallback {
- return (value) => {
- refs.forEach((ref) => {
- if (typeof ref === 'function') {
- ref(value);
- } else if (ref) {
- ref.current = value;
- }
- });
- };
-}
diff --git a/docs/lib/source.ts b/docs/lib/source.ts
deleted file mode 100644
index 1068bc6..0000000
--- a/docs/lib/source.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { type InferPageType, loader } from "fumadocs-core/source";
-import { icons } from "lucide-react";
-import { createElement } from "react";
-import { docs } from "@/.source";
-
-// See https://fumadocs.vercel.app/docs/headless/source-api for more info
-export const source = loader({
- baseUrl: "/docs",
- source: docs.toFumadocsSource(),
- icon(icon) {
- if (!icon) return;
- if (icon in icons) return createElement(icons[icon as keyof typeof icons]);
- },
-});
-
-export function getPageImage(page: InferPageType) {
- const segments = [...page.slugs, "image.png"];
-
- return {
- segments,
- url: `/og/docs/${segments.join("/")}`,
- };
-}
-
-export async function getLLMText(page: InferPageType) {
- const processed = await page.data.getText("processed");
-
- return `# ${page.data.title} (${page.url})
-
-${processed}`;
-}
diff --git a/docs/lib/utils.ts b/docs/lib/utils.ts
deleted file mode 100644
index bd0c391..0000000
--- a/docs/lib/utils.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { clsx, type ClassValue } from "clsx"
-import { twMerge } from "tailwind-merge"
-
-export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
-}
diff --git a/docs/mdx-components.tsx b/docs/mdx-components.tsx
deleted file mode 100644
index 1015181..0000000
--- a/docs/mdx-components.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { ImageZoom } from "fumadocs-ui/components/image-zoom";
-import defaultComponents from "fumadocs-ui/mdx";
-import type { MDXComponents } from "mdx/types";
-
-export function getMDXComponents(components?: MDXComponents): MDXComponents {
- return {
- ...defaultComponents,
- img: (props) => ,
- ...components,
- };
-}
diff --git a/docs/next.config.mjs b/docs/next.config.mjs
deleted file mode 100644
index 0e6c632..0000000
--- a/docs/next.config.mjs
+++ /dev/null
@@ -1,13 +0,0 @@
-import { createMDX } from 'fumadocs-mdx/next';
-
-const withMDX = createMDX();
-
-/** @type {import('next').NextConfig} */
-const config = {
- reactStrictMode: true,
- images: {
- unoptimized: true
- }
-};
-
-export default withMDX(config);
diff --git a/docs/package.json b/docs/package.json
deleted file mode 100644
index 52dd9d0..0000000
--- a/docs/package.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "name": "ckan-devstaller-docs",
- "version": "0.0.0",
- "private": true,
- "scripts": {
- "build": "next build",
- "dev": "next dev --turbo",
- "start": "next start",
- "postinstall": "fumadocs-mdx",
- "lint": "biome check",
- "format": "biome format --write"
- },
- "dependencies": {
- "@radix-ui/react-accordion": "^1.2.12",
- "@radix-ui/react-collapsible": "^1.1.12",
- "@radix-ui/react-label": "^2.1.7",
- "@radix-ui/react-switch": "^1.2.6",
- "@radix-ui/react-tabs": "^1.1.13",
- "class-variance-authority": "^0.7.1",
- "clsx": "^2.1.1",
- "fumadocs-core": "15.8.1",
- "fumadocs-mdx": "12.0.1",
- "fumadocs-ui": "15.8.1",
- "lucide-react": "^0.545.0",
- "next": "15.5.4",
- "next-themes": "^0.4.6",
- "react": "^19.1.1",
- "react-dom": "^19.1.1",
- "sonner": "^2.0.7",
- "tailwind-merge": "^3.3.1"
- },
- "devDependencies": {
- "@biomejs/biome": "2.2.6",
- "@tailwindcss/postcss": "^4.1.13",
- "@types/mdx": "^2.0.13",
- "@types/node": "24.5.2",
- "@types/react": "^19.1.14",
- "@types/react-dom": "^19.1.9",
- "postcss": "^8.5.6",
- "tailwindcss": "^4.1.13",
- "tw-animate-css": "^1.4.0",
- "typescript": "^5.9.2"
- }
-}
\ No newline at end of file
diff --git a/docs/postcss.config.mjs b/docs/postcss.config.mjs
deleted file mode 100644
index a34a3d5..0000000
--- a/docs/postcss.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- plugins: {
- '@tailwindcss/postcss': {},
- },
-};
diff --git a/docs/public/docs-home-example.png b/docs/public/docs-home-example.png
deleted file mode 100644
index 777f04a..0000000
Binary files a/docs/public/docs-home-example.png and /dev/null differ
diff --git a/docs/source.config.ts b/docs/source.config.ts
deleted file mode 100644
index e49b84a..0000000
--- a/docs/source.config.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { remarkMdxFiles } from 'fumadocs-core/mdx-plugins';
-import {
- defineConfig,
- defineDocs,
- frontmatterSchema,
- metaSchema,
-} from 'fumadocs-mdx/config';
-
-// You can customise Zod schemas for frontmatter and `meta.json` here
-// see https://fumadocs.dev/docs/mdx/collections
-export const docs = defineDocs({
- docs: {
- schema: frontmatterSchema,
- postprocess: {
- includeProcessedMarkdown: true,
- },
- },
- meta: {
- schema: metaSchema,
- },
-});
-
-export default defineConfig({
- mdxOptions: {
- remarkPlugins: [remarkMdxFiles],
- },
-});
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
deleted file mode 100644
index 4fd260f..0000000
--- a/docs/tsconfig.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "compilerOptions": {
- "baseUrl": ".",
- "target": "ESNext",
- "lib": ["dom", "dom.iterable", "esnext"],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "preserve",
- "incremental": true,
- "paths": {
- "@/.source": ["./.source/index.ts"],
- "@/*": ["./*"]
- },
- "plugins": [
- {
- "name": "next"
- }
- ]
- },
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
-}
diff --git a/install.bash b/install.bash
index b8b0001..11e582f 100644
--- a/install.bash
+++ b/install.bash
@@ -11,25 +11,10 @@ sudo apt install curl -y
cd ~/
# Download the ckan-devstaller binary file
-curl -LO https://github.com/dathere/ckan-devstaller/releases/download/0.3.1/ckan-devstaller
+curl -LO https://github.com/dathere/ckan-devstaller/releases/download/0.1.0/ckan-devstaller
# Add execute permission to ckan-devstaller binary file
sudo chmod +x ./ckan-devstaller
-# Run the ckan-devstaller binary file with the specified preset and (non-)interactive mode
-preset=$1
-skip_interactive=$2
-
-if [ $preset == "dathere-default" ]; then
- if [ $skip_interactive == "skip-interactive" ]; then
- ./ckan-devstaller --ckan-version 2.11.4 --extensions ckanext-scheming DataStore DataPusher+ --features enable-ssh --skip-interactive
- else
- ./ckan-devstaller --ckan-version 2.11.4 --extensions ckanext-scheming DataStore DataPusher+ --features enable-ssh
- fi
-else
- if [ $preset == "skip-interactive" ]; then
- ./ckan-devstaller --skip-interactive
- else
- ./ckan-devstaller
- fi
-fi
+# Run the ckan-devstaller binary file
+./ckan-devstaller
diff --git a/src/main.rs b/src/main.rs
index f304792..c7c5daa 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,224 +1,159 @@
-mod questions;
-mod steps;
-mod styles;
-
-use crate::{
- questions::{question_ckan_version, question_ssh, question_sysadmin},
- steps::{
- step_install_ahoy, step_install_and_run_ckan_compose,
- step_install_ckanext_scheming_extension, step_install_curl,
- step_install_datapusher_plus_extension, step_install_datastore_extension,
- step_install_docker, step_install_openssh, step_package_updates,
- },
- styles::{important_text, step_text, success_text},
-};
use anyhow::Result;
-use clap::{Parser, Subcommand};
-use human_panic::{metadata, setup_panic};
+use clap::Parser;
use inquire::Confirm;
+use owo_colors::{OwoColorize, Stream::Stdout, Style};
+use serde_json::json;
use std::{path::PathBuf, str::FromStr};
use xshell::cmd;
use xshell_venv::{Shell, VirtualEnv};
-/// CLI to help install a CKAN instance for development within minutes. Learn more at: https://ckan-devstaller.dathere.com
-#[derive(Parser)]
+/// ckan-devstaller CLI
+#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
- /// Skip interactive steps
+ /// Skip interactive steps and install CKAN with default features
#[arg(short, long)]
- skip_interactive: bool,
- /// Skip running CKAN at the end of installation
- #[arg(short, long)]
- skip_run: bool,
- #[arg(short, long)]
- /// CKAN version to install defined by semantic versioning from official releases from https://github.com/ckan/ckan
- ckan_version: Option,
- /// List of CKAN extensions to install, separated by spaces
- #[arg(short, long, value_parser, num_args = 1.., value_delimiter = ' ')]
- extensions: Option>,
- /// List of custom features, separated by spaces
- #[arg(short, long, value_parser, num_args = 1.., value_delimiter = ' ')]
- features: Option>,
- #[command(subcommand)]
- command: Option,
-}
-
-#[derive(Subcommand)]
-enum Commands {
- /// Attempt to uninstall CKAN and related ckan-devstaller installation files
- Uninstall {},
-}
-
-#[derive(Clone)]
-struct Sysadmin {
- username: String,
- password: String,
- email: String,
-}
-
-struct Config {
- ssh: bool,
- ckan_version: String,
- sysadmin: Sysadmin,
- extension_datastore: bool,
- extension_ckanext_scheming: bool,
- extension_datapusher_plus: bool,
- druf_mode: bool,
+ default: bool,
}
fn main() -> Result<()> {
- setup_panic!(metadata!()
- .homepage("https://dathere.com")
- .support("- Create a support ticket at https://support.dathere.com or report an issue at https://github.com/dathere/ckan-devstaller"));
-
- // Set up default config
let args = Args::parse();
- let sh = Shell::new()?;
- let username = cmd!(sh, "whoami").read()?;
- if matches!(&args.command, Some(Commands::Uninstall {})) {
- let uninstall_confirmation = Confirm::new(
- "Are you sure you want to uninstall CKAN and related files from ckan-devstaller?",
- )
- .with_help_message(
- r#"The following commands are ran when attempting the uninstall:
-sudo rm -rf /usr/lib/ckan
-sudo rm -rf /etc/ckan
-cd ~/
-rm -rf qsv*
-rm -rf README ckan-compose ahoy dpp_default_config.ini get-docker.sh permissions.sql"#,
- )
- .prompt()?;
- if uninstall_confirmation {
- cmd!(sh, "sudo rm -rf /usr/lib/ckan").run()?;
- cmd!(sh, "sudo rm -rf /etc/ckan").run()?;
- sh.change_dir(format!("/home/{username}"));
- cmd!(sh, "rm -rf qsv*").run()?;
- cmd!(sh, "rm -rf README ckan-compose ahoy dpp_default_config.ini get-docker.sh permissions.sql").run()?;
- } else {
- println!("Cancelling command.");
- }
- return Ok(());
- }
+ // Color styles
+ let highlight_style = Style::new().on_blue().white();
+ let important_style = Style::new().on_bright_red().white();
+ let step_style = Style::new().on_magenta().white();
+ let success_style = Style::new().on_green().white();
- let default_sysadmin = Sysadmin {
- username: username.clone(),
- password: "password".to_string(),
- email: format!("{username}@localhost"),
- };
- let config = Config {
- ssh: args
- .features
- .is_some_and(|features| features.contains(&"enable-ssh".to_string())),
- ckan_version: if args.ckan_version.is_some() {
- args.ckan_version.unwrap()
- } else {
- "2.11.4".to_string()
- },
- sysadmin: default_sysadmin.clone(),
- extension_datastore: args
- .extensions
- .clone()
- .is_some_and(|extensions| extensions.contains(&"DataStore".to_string())),
- extension_ckanext_scheming: args
- .extensions
- .clone()
- .is_some_and(|extensions| extensions.contains(&"ckanext-scheming".to_string())),
- extension_datapusher_plus: args
- .extensions
- .is_some_and(|extensions| extensions.contains(&"DataPusher+".to_string())),
- druf_mode: false,
- };
+ println!("Welcome to the ckan-devstaller!");
+ println!(
+ "ckan-devstaller is provided by datHere - {}\n",
+ "https://datHere.com".if_supports_color(Stdout, |t| t.style(highlight_style)),
+ );
+ println!(
+ "This installer should assist in setting up {} from a source installation along with ckan-compose (https://github.com/tino097/ckan-compose). If you have any issues, please report them at https://github.com/dathere/ckan-devstaller/issues.",
+ "CKAN 2.11.3".if_supports_color(Stdout, |t| t.style(highlight_style))
+ );
+ println!(
+ "{}",
+ "This installer is only intended for a brand new installation of Ubuntu 22.04."
+ .if_supports_color(Stdout, |t| t.style(important_style))
+ );
- steps::step_intro();
-
- let mut default_config_text =
- String::from("The current configuration for ckan-devstaller does the following:");
- if config.ssh {
- default_config_text.push_str("\n- Install openssh-server to enable SSH access");
- }
- default_config_text.push_str("\n- Install ckan-compose (https://github.com/tino097/ckan-compose/tree/ckan-devstaller) which sets up the CKAN backend (PostgreSQL, SOLR, Redis)");
- default_config_text.push_str(format!("\n- Install CKAN v{}", config.ckan_version).as_str());
- if config.extension_datastore {
- default_config_text.push_str("\n- Install the DataStore extension");
- }
- if config.extension_ckanext_scheming {
- default_config_text.push_str("\n- Install the ckanext-scheming extension");
- }
- if config.extension_datapusher_plus {
- default_config_text.push_str("\n- Install the DataPusher+ extension");
- default_config_text.push_str("\n- Disable DRUF mode for DataPusher+");
- }
- println!("{default_config_text}");
- let answer_customize = if args.skip_interactive {
- false
- } else {
- Confirm::new("Would you like to customize the configuration for your CKAN installation?")
- .prompt()?
- };
- let config = if answer_customize {
- let answer_ssh = question_ssh()?;
- let answer_ckan_version = question_ckan_version()?;
- let answer_sysadmin = question_sysadmin(username.clone())?;
- // let answer_extension_datastore = Confirm::new("Would you like to install the DataStore extension?")
- // .with_default(true)
- // .prompt()?;
- // let answer_extension_ckanext_scheming = Confirm::new("Would you like to install the ckanext-scheming extension?")
- // .with_default(true)
- // .prompt()?;
- let answer_extension_datapusher_plus =
- Confirm::new("Would you like to install the DataPusher+ extension?")
- .with_default(true)
- .prompt()?;
- let answer_druf_mode = if answer_extension_datapusher_plus {
- Confirm::new("Would you like to enable DRUF mode for DataPusher+?")
- .with_default(false)
- .prompt()?
- } else {
- false
- };
- Config {
- ssh: answer_ssh,
- ckan_version: answer_ckan_version,
- sysadmin: answer_sysadmin,
- extension_datastore: true,
- extension_ckanext_scheming: true,
- extension_datapusher_plus: answer_extension_datapusher_plus,
- druf_mode: answer_druf_mode,
- }
- } else {
- config
- };
-
- let begin_installation = if args.skip_interactive {
+ let ans = if args.default {
true
} else {
- Confirm::new("Would you like to begin the installation?").prompt()?
+ Confirm::new("Would you like to begin the installation?")
+ .with_default(false)
+ .prompt()?
};
- if begin_installation {
- println!("\n{}", important_text("Starting installation..."));
- // Run sudo apt update and sudo apt upgrade
- step_package_updates("1.".to_string(), &sh)?;
-
- // Install curl
- step_install_curl("2.".to_string(), &sh)?;
- // If user wants SSH capability, install openssh-server
- if config.ssh {
- step_install_openssh("2.".to_string(), &sh)?;
- }
-
- // Install docker CLI if user does not have it installed
- step_install_docker("3.".to_string(), &sh, username.clone())?;
-
- step_install_ahoy("4.".to_string(), &sh, username.clone())?;
-
- step_install_and_run_ckan_compose("5.".to_string(), &sh, username.clone())?;
+ if ans {
+ let sh = Shell::new()?;
+ let username = cmd!(sh, "whoami").read()?;
+ println!(
+ "\n{} Running {} and {}...",
+ "1.".if_supports_color(Stdout, |t| t.style(step_style)),
+ "sudo apt update -y".if_supports_color(Stdout, |t| t.style(highlight_style)),
+ "sudo apt upgrade -y".if_supports_color(Stdout, |t| t.style(highlight_style))
+ );
+ println!(
+ "{}",
+ "You may need to provide your sudo password."
+ .if_supports_color(Stdout, |t| t.style(important_style))
+ );
+ cmd!(sh, "sudo apt update -y").run()?;
+ // Ignoring xrdp error with .ignore_status() for now
+ cmd!(sh, "sudo apt upgrade -y").ignore_status().run()?;
+ println!(
+ "{}",
+ "✅ 1. Successfully ran update and upgrade commands."
+ .if_supports_color(Stdout, |t| t.style(success_style))
+ );
println!(
- "\n{} Installing CKAN {}...",
- step_text("6."),
- config.ckan_version
+ "\n{} Installing curl and enabling SSH...",
+ "2.".if_supports_color(Stdout, |t| t.style(step_style)),
+ );
+ cmd!(sh, "sudo apt install curl openssh-server -y").run()?;
+ println!(
+ "{}",
+ "✅ 2. Successfully installed curl and enabled SSH."
+ .if_supports_color(Stdout, |t| t.style(success_style))
+ );
+
+ let dpkg_l_output = cmd!(sh, "dpkg -l").read()?;
+ let has_docker = cmd!(sh, "grep docker")
+ .stdin(dpkg_l_output.clone())
+ .ignore_status()
+ .output()?
+ .status
+ .success();
+ if !has_docker {
+ println!(
+ "{} Installing Docker...",
+ "3.".if_supports_color(Stdout, |t| t.style(step_style)),
+ );
+ cmd!(
+ sh,
+ "curl -fsSL https://get.docker.com -o /home/{username}/get-docker.sh"
+ )
+ .run()?;
+ cmd!(sh, "sudo sh /home/{username}/get-docker.sh").run()?;
+ println!(
+ "{}",
+ "✅ 3. Successfully installed Docker."
+ .if_supports_color(Stdout, |t| t.style(success_style))
+ );
+ }
+
+ let has_docker_compose = cmd!(sh, "grep docker-compose")
+ .stdin(dpkg_l_output)
+ .ignore_status()
+ .output()?
+ .status
+ .success();
+ if !has_docker_compose {
+ cmd!(sh, "sudo apt install docker-compose -y").run()?;
+ }
+
+ println!(
+ "\n{} Installing Ahoy...",
+ "4.".if_supports_color(Stdout, |t| t.style(step_style)),
+ );
+ sh.change_dir(format!("/home/{username}"));
+ cmd!(sh, "sudo curl -LO https://github.com/ahoy-cli/ahoy/releases/download/v2.5.0/ahoy-bin-linux-amd64").run()?;
+ cmd!(sh, "mv ./ahoy-bin-linux-amd64 ./ahoy").run()?;
+ cmd!(sh, "sudo chmod +x ./ahoy").run()?;
+ println!(
+ "{}",
+ "✅ 4. Successfully installed Ahoy."
+ .if_supports_color(Stdout, |t| t.style(success_style))
+ );
+
+ println!(
+ "\n{} Downloading, installing, and starting ckan-compose...",
+ "5.".if_supports_color(Stdout, |t| t.style(step_style)),
+ );
+ if !std::fs::exists(format!("/home/{username}/ckan-compose"))? {
+ cmd!(sh, "git clone https://github.com/tino097/ckan-compose.git").run()?;
+ }
+ sh.change_dir(format!("/home/{username}/ckan-compose"));
+ cmd!(sh, "git switch solr-9-impl").run()?;
+ let env_data = "PROJECT_NAME=ckan-devstaller-project
+DATASTORE_READONLY_PASSWORD=pass
+POSTGRES_PASSWORD=pass";
+ std::fs::write(format!("/home/{username}/ckan-compose/.env"), env_data)?;
+ cmd!(sh, "sudo ../ahoy up").run()?;
+ println!(
+ "{}",
+ "✅ 5. Successfully ran ckan-compose."
+ .if_supports_color(Stdout, |t| t.style(success_style))
+ );
+
+ println!(
+ "\n{} Installing CKAN 2.11.3...",
+ "6.".if_supports_color(Stdout, |t| t.style(step_style)),
);
cmd!(sh, "sudo apt install python3-dev libpq-dev python3-pip python3-venv git-core redis-server -y").run()?;
cmd!(sh, "sudo mkdir -p /usr/lib/ckan/default").run()?;
@@ -227,20 +162,16 @@ rm -rf README ckan-compose ahoy dpp_default_config.ini get-docker.sh permissions
let venv = VirtualEnv::with_path(&sh, &venv_path)?;
venv.pip_upgrade("pip")?;
venv.pip_install(
- format!(
- "git+https://github.com/ckan/ckan.git@ckan-{}#egg=ckan[requirements]",
- config.ckan_version
- )
- .as_str(),
+ "git+https://github.com/ckan/ckan.git@ckan-2.11.3#egg=ckan[requirements]",
)?;
cmd!(sh, "sudo mkdir -p /etc/ckan/default").run()?;
cmd!(sh, "sudo chown -R {username} /etc/ckan/").run()?;
cmd!(
sh,
- "git clone https://github.com/ckan/ckan.git /usr/lib/ckan/default/src/ckan"
+ "git clone https://github.com/ckan/ckan.git /usr/lib/ckan/default/src"
)
.run()?;
- sh.change_dir("/usr/lib/ckan/default/src/ckan");
+ sh.change_dir("/usr/lib/ckan/default/src");
cmd!(sh, "ckan generate config /etc/ckan/default/ckan.ini").run()?;
cmd!(
sh,
@@ -253,43 +184,231 @@ rm -rf README ckan-compose ahoy dpp_default_config.ini get-docker.sh permissions
cmd!(sh, "sudo mkdir -p ckan/default").run()?;
cmd!(sh, "sudo chown {username}.{username} ckan/default").run()?;
cmd!(sh, "ckan -c /etc/ckan/default/ckan.ini db init").run()?;
- let sysadmin_username = config.sysadmin.username;
- let sysadmin_password = config.sysadmin.password;
- let sysadmin_email = config.sysadmin.email;
- cmd!(sh, "ckan -c /etc/ckan/default/ckan.ini user add {sysadmin_username} password={sysadmin_password} email={sysadmin_email}").run()?;
+ cmd!(sh, "ckan -c /etc/ckan/default/ckan.ini user add {username} password=password email={username}@localhost").run()?;
cmd!(
sh,
- "ckan -c /etc/ckan/default/ckan.ini sysadmin add {sysadmin_username}"
+ "ckan -c /etc/ckan/default/ckan.ini sysadmin add {username}"
)
.run()?;
println!(
"{}",
- success_text(format!("6. Installed CKAN {}.", config.ckan_version).as_str())
+ "✅ 6. Installed CKAN 2.11.3."
+ .if_supports_color(Stdout, |t| t.style(success_style))
);
- // Install extensions
- if config.extension_datastore {
- step_install_datastore_extension("7.".to_string(), &sh, username.clone())?;
- }
- if config.extension_ckanext_scheming {
- step_install_ckanext_scheming_extension("8.".to_string(), &sh, username.clone())?;
- }
- if config.extension_datapusher_plus {
- step_install_datapusher_plus_extension(
- "9.".to_string(),
- &sh,
- sysadmin_username,
- username.clone(),
- )?;
+ println!(
+ "\n{} Enabling DataStore plugin, adding config URLs in /etc/ckan/default/ckan.ini and updating permissions...",
+ "7.".if_supports_color(Stdout, |t| t.style(step_style)),
+ );
+ // TODO: use the ckan config-tool command instead of rust-ini
+ let mut conf = ini::Ini::load_from_file("/etc/ckan/default/ckan.ini")?;
+ let app_main_section = conf.section_mut(Some("app:main")).unwrap();
+ let mut ckan_plugins = app_main_section.get("ckan.plugins").unwrap().to_string();
+ ckan_plugins.push_str(" datastore");
+ app_main_section.insert("ckan.plugins", ckan_plugins);
+ app_main_section.insert(
+ "ckan.datastore.write_url",
+ "postgresql://ckan_default:pass@localhost/datastore_default",
+ );
+ app_main_section.insert(
+ "ckan.datastore.read_url",
+ "postgresql://datastore_default:pass@localhost/datastore_default",
+ );
+ app_main_section.insert("ckan.datastore.sqlsearch.enabled", "true");
+ conf.write_to_file("/etc/ckan/default/ckan.ini")?;
+ let postgres_container_id = cmd!(
+ sh,
+ "sudo docker ps -aqf name=^ckan-devstaller-project-postgres$"
+ )
+ .read()?;
+ let set_permissions_output = cmd!(
+ sh,
+ "ckan -c /etc/ckan/default/ckan.ini datastore set-permissions"
+ )
+ .read()?;
+ std::fs::write("permissions.sql", set_permissions_output)?;
+ loop {
+ std::thread::sleep(std::time::Duration::from_secs(2));
+ if std::fs::exists("permissions.sql")? {
+ break;
+ }
}
+ sh.change_dir(format!("/home/{username}"));
+ cmd!(
+ sh,
+ "sudo docker cp permissions.sql {postgres_container_id}:/permissions.sql"
+ )
+ .run()?;
+ cmd!(sh, "sudo docker exec {postgres_container_id} psql -U ckan_default --set ON_ERROR_STOP=1 -f permissions.sql").run()?;
+ println!(
+ "{}",
+ "✅ 7. Enabled DataStore plugin, set DataStore URLs in /etc/ckan/default/ckan.ini, and updated permissions."
+ .if_supports_color(Stdout, |t| t.style(success_style))
+ );
- if !args.skip_run {
- println!("\n{}", success_text("Running CKAN instance..."));
- cmd!(sh, "ckan -c /etc/ckan/default/ckan.ini run").run()?;
- }
- } else {
- println!("Cancelling installation.");
+ println!(
+ "\n{} Installing ckanext-scheming and DataPusher+ extensions...",
+ "8.".if_supports_color(Stdout, |t| t.style(step_style)),
+ );
+ cmd!(
+ sh,
+ "pip install -e git+https://github.com/ckan/ckanext-scheming.git#egg=ckanext-scheming"
+ )
+ .run()?;
+ let mut conf = ini::Ini::load_from_file("/etc/ckan/default/ckan.ini")?;
+ let app_main_section = conf.section_mut(Some("app:main")).unwrap();
+ let mut ckan_plugins = app_main_section.get("ckan.plugins").unwrap().to_string();
+ ckan_plugins.push_str(" scheming_datasets");
+ cmd!(sh, "ckan config-tool /etc/ckan/default/ckan.ini -s app:main ckan.plugins={ckan_plugins}").run()?;
+ cmd!(sh, "ckan config-tool /etc/ckan/default/ckan.ini -s app:main scheming.presets=ckanext.scheming:presets.json").run()?;
+ cmd!(sh, "ckan config-tool /etc/ckan/default/ckan.ini -s app:main scheming.dataset_fallback=false").run()?;
+ // app_main_section.insert("ckan.plugins", ckan_plugins);
+ // app_main_section.insert("scheming.presets", "ckanext.scheming:presets.json");
+ // app_main_section.insert("scheming.dataset_fallback", "false");
+ // conf.write_to_file("/etc/ckan/default/ckan.ini")?;
+ // Install DataPusher+
+ cmd!(sh, "sudo apt install python3-virtualenv python3-dev python3-pip python3-wheel build-essential libxslt1-dev libxml2-dev zlib1g-dev git libffi-dev libpq-dev uchardet -y").run()?;
+ sh.change_dir("/usr/lib/ckan/default/src");
+ cmd!(sh, "pip install -e git+https://github.com/dathere/datapusher-plus.git@main#egg=datapusher-plus").run()?;
+ sh.change_dir("/usr/lib/ckan/default/src/datapusher-plus");
+ cmd!(sh, "pip install -r requirements.txt").run()?;
+ sh.change_dir(format!("/home/{username}"));
+ cmd!(sh, "wget https://github.com/dathere/qsv/releases/download/4.0.0/qsv-4.0.0-x86_64-unknown-linux-gnu.zip").run()?;
+ cmd!(sh, "sudo apt install unzip -y").run()?;
+ cmd!(sh, "unzip qsv-4.0.0-x86_64-unknown-linux-gnu.zip").run()?;
+ cmd!(sh, "sudo rm -rf qsv-4.0.0-x86_64-unknown-linux-gnu.zip").run()?;
+ cmd!(sh, "sudo mv ./qsvdp_glibc-2.31 /usr/local/bin/qsvdp").run()?;
+ let mut conf = ini::Ini::load_from_file("/etc/ckan/default/ckan.ini")?;
+ let app_main_section = conf.section_mut(Some("app:main")).unwrap();
+ let mut ckan_plugins = app_main_section.get("ckan.plugins").unwrap().to_string();
+ ckan_plugins.push_str(" datapusher_plus");
+ cmd!(sh, "ckan config-tool /etc/ckan/default/ckan.ini -s app:main ckan.plugins={ckan_plugins}").run()?;
+ cmd!(sh, "ckan config-tool /etc/ckan/default/ckan.ini -s app:main scheming.dataset_schemas=ckanext.datapusher_plus:dataset-druf.yaml").run()?;
+ // app_main_section.insert("ckan.plugins", ckan_plugins);
+ // app_main_section.insert(
+ // "scheming.dataset_schemas",
+ // "ckanext.datapusher_plus:dataset-druf.yaml",
+ // );
+ // conf.write_to_file("/etc/ckan/default/ckan.ini")?;
+ let dpp_default_config = r#"
+ckanext.datapusher_plus.use_proxy = false
+ckanext.datapusher_plus.download_proxy =
+ckanext.datapusher_plus.ssl_verify = false
+# supports INFO, DEBUG, TRACE - use DEBUG or TRACE when debugging scheming Formulas
+ckanext.datapusher_plus.upload_log_level = INFO
+ckanext.datapusher_plus.formats = csv tsv tab ssv xls xlsx xlsxb xlsm ods geojson shp qgis zip
+ckanext.datapusher_plus.pii_screening = false
+ckanext.datapusher_plus.pii_found_abort = false
+ckanext.datapusher_plus.pii_regex_resource_id_or_alias =
+ckanext.datapusher_plus.pii_show_candidates = false
+ckanext.datapusher_plus.pii_quick_screen = false
+ckanext.datapusher_plus.qsv_bin = /usr/local/bin/qsvdp
+ckanext.datapusher_plus.preview_rows = 100
+ckanext.datapusher_plus.download_timeout = 300
+ckanext.datapusher_plus.max_content_length = 1256000000000
+ckanext.datapusher_plus.chunk_size = 16384
+ckanext.datapusher_plus.default_excel_sheet = 0
+ckanext.datapusher_plus.sort_and_dupe_check = true
+ckanext.datapusher_plus.dedup = false
+ckanext.datapusher_plus.unsafe_prefix = unsafe_
+ckanext.datapusher_plus.reserved_colnames = _id
+ckanext.datapusher_plus.prefer_dmy = false
+ckanext.datapusher_plus.ignore_file_hash = true
+ckanext.datapusher_plus.auto_index_threshold = 3
+ckanext.datapusher_plus.auto_index_dates = true
+ckanext.datapusher_plus.auto_unique_index = true
+ckanext.datapusher_plus.summary_stats_options =
+ckanext.datapusher_plus.add_summary_stats_resource = false
+ckanext.datapusher_plus.summary_stats_with_preview = false
+ckanext.datapusher_plus.qsv_stats_string_max_length = 32767
+ckanext.datapusher_plus.qsv_dates_whitelist = date,time,due,open,close,created
+ckanext.datapusher_plus.qsv_freq_limit = 10
+ckanext.datapusher_plus.auto_alias = true
+ckanext.datapusher_plus.auto_alias_unique = false
+ckanext.datapusher_plus.copy_readbuffer_size = 1048576
+ckanext.datapusher_plus.type_mapping = {"String": "text", "Integer": "numeric","Float": "numeric","DateTime": "timestamp","Date": "date","NULL": "text"}
+ckanext.datapusher_plus.auto_spatial_simplication = true
+ckanext.datapusher_plus.spatial_simplication_relative_tolerance = 0.1
+ckanext.datapusher_plus.latitude_fields = latitude,lat
+ckanext.datapusher_plus.longitude_fields = longitude,long,lon
+ckanext.datapusher_plus.jinja2_bytecode_cache_dir = /tmp/jinja2_butecode_cache
+ckanext.datapusher_plus.auto_unzip_one_file = true
+ckanext.datapusher_plus.api_token =
+ckanext.datapusher_plus.describeGPT_api_key =
+ckanext.datapusher_plus.file_bin = /usr/bin/file
+ckanext.datapusher_plus.enable_druf = true
+ckanext.datapusher_plus.enable_form_redirect = true
+"#;
+ std::fs::write("dpp_default_config.ini", dpp_default_config)?;
+ cmd!(
+ sh,
+ "ckan config-tool /etc/ckan/default/ckan.ini -f dpp_default_config.ini"
+ )
+ .run()?;
+ let resource_formats_str =
+ std::fs::read_to_string("/usr/lib/ckan/default/src/ckan/config/resource_formats.json")?;
+ let mut resource_formats_val: serde_json::Value =
+ serde_json::from_str(&resource_formats_str)?;
+ let all_resource_formats = resource_formats_val
+ .get_mut(0)
+ .unwrap()
+ .as_array_mut()
+ .unwrap();
+ all_resource_formats.push(json!([
+ "TAB",
+ "Tab Separated Values File",
+ "text/tab-separated-values",
+ []
+ ]));
+ std::fs::write(
+ "/usr/lib/ckan/default/src/ckan/config/resource_formats.json",
+ serde_json::to_string(&resource_formats_val)?,
+ )?;
+ cmd!(sh, "sudo locale-gen en_US.UTF-8").run()?;
+ cmd!(sh, "sudo update-locale").run()?;
+ let token_command_output = cmd!(
+ sh,
+ "ckan -c /etc/ckan/default/ckan.ini user token add {username} dpplus"
+ )
+ .read()?;
+ let tail_output = cmd!(sh, "tail -n 1").stdin(token_command_output).read()?;
+ let dpp_api_token = cmd!(sh, "tr -d '\t'").stdin(tail_output).read()?;
+ cmd!(sh, "ckan config-tool /etc/ckan/default/ckan.ini ckanext.datapusher_plus.api_token={dpp_api_token}").env("LC_ALL", "en_US.UTF-8").run()?;
+ cmd!(
+ sh,
+ "ckan -c /etc/ckan/default/ckan.ini db upgrade -p datapusher_plus"
+ )
+ .run()?;
+ println!(
+ "{}",
+ "✅ 8. Installed ckanext-scheming and DataPusher+ extensions."
+ .if_supports_color(Stdout, |t| t.style(success_style))
+ );
+
+ println!(
+ "\n{}",
+ "✅ 9. Running CKAN instance...".if_supports_color(Stdout, |t| t.style(success_style))
+ );
+ cmd!(sh, "ckan -c /etc/ckan/default/ckan.ini run").run()?;
}
Ok(())
}
+
+struct Config {
+ ssh: bool,
+}
+
+fn get_config_from_prompts() -> Result {
+ let ssh = Confirm::new("Would you like to enable SSH? (optional)")
+ .with_default(false)
+ .with_help_message(
+ format!(
+ "This step would install {}",
+ "openssh-server".if_supports_color(Stdout, |t| t.on_blue().white())
+ )
+ .as_str(),
+ )
+ .prompt()?;
+ Ok(Config { ssh })
+}
diff --git a/src/questions.rs b/src/questions.rs
deleted file mode 100644
index 402639e..0000000
--- a/src/questions.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use crate::{Sysadmin, styles::highlighted_text};
-use anyhow::Result;
-use inquire::{Confirm, Select, Text};
-
-pub fn question_ssh() -> Result {
- Ok(Confirm::new("Would you like to enable SSH? (optional)")
- .with_default(false)
- .with_help_message(
- format!(
- "This step would install {}",
- highlighted_text("openssh-server")
- )
- .as_str(),
- )
- .prompt()?)
-}
-
-pub fn question_ckan_version() -> Result {
- let ckan_version_options: Vec<&str> = vec!["2.11.4", "2.10.9", "Other"];
- let answer_ckan_version = Select::new(
- "What CKAN version would you like to install? (optional)",
- ckan_version_options,
- )
- .with_help_message("We recommend using the latest compatible version of CKAN. Please do not choose 'Other' option unless for testing purposes as the CKAN version may not be supported and may cause a broken installation.")
- .prompt()?;
- if answer_ckan_version == "Other" {
- Ok(
- Text::new("What CKAN version would you like to install? (optional)")
- .with_default("2.11.4")
- .prompt()?,
- )
- } else {
- Ok(answer_ckan_version.to_string())
- }
-}
-
-pub fn question_sysadmin(username: String) -> Result {
- let configure_sysadmin = Confirm::new("Would you like to configure the sysadmin account for your CKAN instance?")
- .with_help_message(format!("The following values are set as defaults for the sysadmin account:\n\n- Username: {username}\n- Password: password\n- Email: {username}@localhost\n").as_str())
- .prompt()?;
- if configure_sysadmin {
- let username = Text::new("What should your sysadmin username be set to?")
- .with_default(username.clone().as_str())
- .prompt()?;
- let password = Text::new("What should your sysadmin password be set to?")
- .with_default("password")
- .with_help_message("The password must be at least 8 characters long")
- .prompt()?;
- let email = Text::new("What should your sysadmin email be set to?")
- .with_default(format!("{username}@localhost").as_str())
- .prompt()?;
- Ok(Sysadmin {
- username,
- password,
- email,
- })
- } else {
- Ok(Sysadmin {
- username: username.clone(),
- password: "password".to_string(),
- email: format!("{username}@localhost"),
- })
- }
-}
diff --git a/src/steps.rs b/src/steps.rs
deleted file mode 100644
index af19091..0000000
--- a/src/steps.rs
+++ /dev/null
@@ -1,387 +0,0 @@
-use crate::styles::{highlighted_text, important_text, step_text, success_text};
-use anyhow::Result;
-use serde_json::json;
-use xshell::{Shell, cmd};
-
-pub fn step_intro() {
- println!("Welcome to the ckan-devstaller!");
- println!(
- "ckan-devstaller is provided by datHere - {}\n",
- highlighted_text("https://datHere.com"),
- );
- println!(
- "This installer should assist in setting up {} from a source installation along with ckan-compose. If you have any issues, please report them at https://support.dathere.com or https://github.com/dathere/ckan-devstaller/issues.",
- highlighted_text("CKAN 2.11.4")
- );
- println!(
- "\nYou may also learn more about ckan-devstaller at https://ckan-devstaller.dathere.com."
- );
- println!(
- "\n{}\n",
- important_text(
- "This installer is only intended for a brand new installation of Ubuntu 22.04."
- )
- );
-}
-
-pub fn step_package_updates(step_prefix: String, sh: &Shell) -> Result<()> {
- println!(
- "\n{} Running {} and {}...",
- step_text(step_prefix.as_str()),
- highlighted_text("sudo apt update -y"),
- highlighted_text("sudo apt upgrade -y")
- );
- println!(
- "{}",
- important_text("You may need to provide your sudo password.")
- );
- cmd!(sh, "sudo apt update -y").run()?;
- // Ignoring xrdp error with .ignore_status() for now
- cmd!(sh, "sudo apt upgrade -y").ignore_status().run()?;
- println!(
- "{}",
- success_text(
- format!("{step_prefix} Successfully ran update and upgrade commands.").as_str()
- )
- );
- Ok(())
-}
-
-pub fn step_install_curl(step_prefix: String, sh: &Shell) -> Result<()> {
- println!(
- "\n{} Installing {}...",
- step_text("2."),
- highlighted_text("curl")
- );
- cmd!(sh, "sudo apt install curl -y").run()?;
- println!(
- "{}",
- success_text(format!("{step_prefix} Successfully installed curl.").as_str())
- );
- Ok(())
-}
-
-pub fn step_install_openssh(step_prefix: String, sh: &Shell) -> Result<()> {
- println!(
- "\n{} Installing openssh-server...",
- step_text(step_prefix.as_str())
- );
- cmd!(sh, "sudo apt install openssh-server -y").run()?;
- println!(
- "{}",
- success_text(format!("{step_prefix} Successfully installed openssh-server.").as_str())
- );
- Ok(())
-}
-
-pub fn step_install_docker(step_prefix: String, sh: &Shell, username: String) -> Result<()> {
- let dpkg_l_output = cmd!(sh, "dpkg -l").read()?;
- let has_docker = cmd!(sh, "grep docker")
- .stdin(dpkg_l_output.clone())
- .ignore_status()
- .output()?
- .status
- .success();
- if !has_docker {
- println!("{} Installing Docker...", step_text(step_prefix.as_str()),);
- cmd!(
- sh,
- "curl -fsSL https://get.docker.com -o /home/{username}/get-docker.sh"
- )
- .run()?;
- cmd!(sh, "sudo sh /home/{username}/get-docker.sh").run()?;
- println!(
- "{}",
- success_text(format!("{step_prefix} Successfully installed Docker.").as_str())
- );
- }
- Ok(())
-}
-
-pub fn step_install_ahoy(step_prefix: String, sh: &Shell, username: String) -> Result<()> {
- println!("\n{} Installing Ahoy...", step_text(step_prefix.as_str()),);
- sh.change_dir(format!("/home/{username}"));
- cmd!(sh, "sudo curl -LO https://github.com/ahoy-cli/ahoy/releases/download/v2.5.0/ahoy-bin-linux-amd64").run()?;
- cmd!(sh, "mv ./ahoy-bin-linux-amd64 ./ahoy").run()?;
- cmd!(sh, "sudo chmod +x ./ahoy").run()?;
- println!(
- "{}",
- success_text(format!("{step_prefix} Successfully installed Ahoy.").as_str())
- );
- Ok(())
-}
-
-pub fn step_install_and_run_ckan_compose(
- step_prefix: String,
- sh: &Shell,
- username: String,
-) -> Result<()> {
- println!(
- "\n{} Downloading, installing, and starting ckan-compose...",
- step_text(step_prefix.as_str()),
- );
- if !std::fs::exists(format!("/home/{username}/ckan-compose"))? {
- cmd!(sh, "git clone https://github.com/tino097/ckan-compose.git").run()?;
- }
- sh.change_dir(format!("/home/{username}/ckan-compose"));
- cmd!(sh, "git switch ckan-devstaller").run()?;
- let env_data = "PROJECT_NAME=ckan-devstaller-project
-DATASTORE_READONLY_PASSWORD=pass
-POSTGRES_PASSWORD=pass";
- std::fs::write(format!("/home/{username}/ckan-compose/.env"), env_data)?;
- cmd!(sh, "sudo ../ahoy up").run()?;
- println!(
- "{}",
- success_text(format!("{step_prefix} Successfully ran ckan-compose.").as_str())
- );
- Ok(())
-}
-
-pub fn step_install_datastore_extension(
- step_prefix: String,
- sh: &Shell,
- username: String,
-) -> Result<()> {
- println!(
- "\n{} Enabling DataStore plugin, adding config URLs in /etc/ckan/default/ckan.ini and updating permissions...",
- step_text(step_prefix.as_str()),
- );
- let mut conf = ini::Ini::load_from_file("/etc/ckan/default/ckan.ini")?;
- let app_main_section = conf.section_mut(Some("app:main")).unwrap();
- let mut ckan_plugins = app_main_section.get("ckan.plugins").unwrap().to_string();
- ckan_plugins.push_str(" datastore");
- app_main_section.insert("ckan.plugins", ckan_plugins);
- app_main_section.insert(
- "ckan.datastore.write_url",
- "postgresql://ckan_default:pass@localhost/datastore_default",
- );
- app_main_section.insert(
- "ckan.datastore.read_url",
- "postgresql://datastore_default:pass@localhost/datastore_default",
- );
- app_main_section.insert("ckan.datastore.sqlsearch.enabled", "true");
- conf.write_to_file("/etc/ckan/default/ckan.ini")?;
- let postgres_container_id = cmd!(
- sh,
- "sudo docker ps -aqf name=^ckan-devstaller-project-postgres$"
- )
- .read()?;
- let set_permissions_output = cmd!(
- sh,
- "ckan -c /etc/ckan/default/ckan.ini datastore set-permissions"
- )
- .read()?;
- std::fs::write("permissions.sql", set_permissions_output)?;
- loop {
- std::thread::sleep(std::time::Duration::from_secs(2));
- if std::fs::exists("permissions.sql")? {
- break;
- }
- }
- sh.change_dir(format!("/home/{username}"));
- cmd!(
- sh,
- "sudo docker cp permissions.sql {postgres_container_id}:/permissions.sql"
- )
- .run()?;
- cmd!(sh, "sudo docker exec {postgres_container_id} psql -U ckan_default --set ON_ERROR_STOP=1 -f permissions.sql").run()?;
- println!(
- "{}",
- success_text(
- format!("{step_prefix} Enabled DataStore plugin, set DataStore URLs in /etc/ckan/default/ckan.ini, and updated permissions.").as_str()
- )
- );
- Ok(())
-}
-
-pub fn step_install_ckanext_scheming_extension(
- step_prefix: String,
- sh: &Shell,
- username: String,
-) -> Result<()> {
- println!(
- "{}",
- step_text("\n{} Installing the ckanext-scheming extension..."),
- );
- cmd!(
- sh,
- "pip install -e git+https://github.com/ckan/ckanext-scheming.git#egg=ckanext-scheming"
- )
- .run()?;
- let mut conf = ini::Ini::load_from_file("/etc/ckan/default/ckan.ini")?;
- let app_main_section = conf.section_mut(Some("app:main")).unwrap();
- let mut ckan_plugins = app_main_section.get("ckan.plugins").unwrap().to_string();
- ckan_plugins.push_str(" scheming_datasets");
- cmd!(
- sh,
- "ckan config-tool /etc/ckan/default/ckan.ini -s app:main ckan.plugins={ckan_plugins}"
- )
- .run()?;
- cmd!(sh, "ckan config-tool /etc/ckan/default/ckan.ini -s app:main scheming.presets=ckanext.scheming:presets.json").run()?;
- cmd!(
- sh,
- "ckan config-tool /etc/ckan/default/ckan.ini -s app:main scheming.dataset_fallback=false"
- )
- .run()?;
- // app_main_section.insert("ckan.plugins", ckan_plugins);
- // app_main_section.insert("scheming.presets", "ckanext.scheming:presets.json");
- // app_main_section.insert("scheming.dataset_fallback", "false");
- // conf.write_to_file("/etc/ckan/default/ckan.ini")?;
- Ok(())
-}
-
-pub fn step_install_datapusher_plus_extension(
- step_prefix: String,
- sh: &Shell,
- sysadmin_username: String,
- username: String,
-) -> Result<()> {
- // Install DataPusher+
- println!(
- "{}",
- step_text(format!("\n{step_prefix} Installing DataPusher+ extension...").as_str())
- );
- cmd!(sh, "sudo apt install python3-virtualenv python3-dev python3-pip python3-wheel build-essential libxslt1-dev libxml2-dev zlib1g-dev git libffi-dev libpq-dev uchardet -y").run()?;
- sh.change_dir("/usr/lib/ckan/default/src");
- cmd!(
- sh,
- "pip install -e git+https://github.com/dathere/datapusher-plus.git@main#egg=datapusher-plus"
- )
- .run()?;
- sh.change_dir("/usr/lib/ckan/default/src/datapusher-plus");
- cmd!(sh, "pip install -r requirements.txt").run()?;
- sh.change_dir(format!("/home/{username}"));
- cmd!(sh, "wget https://github.com/dathere/qsv/releases/download/4.0.0/qsv-4.0.0-x86_64-unknown-linux-gnu.zip").run()?;
- cmd!(sh, "sudo apt install unzip -y").run()?;
- cmd!(sh, "unzip qsv-4.0.0-x86_64-unknown-linux-gnu.zip").run()?;
- cmd!(sh, "sudo rm -rf qsv-4.0.0-x86_64-unknown-linux-gnu.zip").run()?;
- cmd!(sh, "sudo mv ./qsvdp_glibc-2.31 /usr/local/bin/qsvdp").run()?;
- let mut conf = ini::Ini::load_from_file("/etc/ckan/default/ckan.ini")?;
- let app_main_section = conf.section_mut(Some("app:main")).unwrap();
- let mut ckan_plugins = app_main_section.get("ckan.plugins").unwrap().to_string();
- ckan_plugins.push_str(" datapusher_plus");
- app_main_section.insert("ckan.plugins", ckan_plugins);
- app_main_section.insert(
- "scheming.dataset_schemas",
- "ckanext.datapusher_plus:dataset-druf.yaml",
- );
- app_main_section.insert("ckanext.datapusher_plus.use_proxy", "false");
- app_main_section.insert("ckanext.datapusher_plus.download_proxy", "");
- app_main_section.insert("ckanext.datapusher_plus.ssl_verify", "false");
- app_main_section.insert("ckanext.datapusher_plus.upload_log_level", "INFO");
- app_main_section.insert(
- "ckanext.datapusher_plus.formats",
- "csv tsv tab ssv xls xlsx xlsxb xlsm ods geojson shp qgis zip",
- );
- app_main_section.insert("ckanext.datapusher_plus.pii_screening", "false");
- app_main_section.insert("ckanext.datapusher_plus.pii_found_abort", "false");
- app_main_section.insert("ckanext.datapusher_plus.pii_regex_resource_id_or_alias", "");
- app_main_section.insert("ckanext.datapusher_plus.pii_show_candidates", "false");
- app_main_section.insert("ckanext.datapusher_plus.pii_quick_screen", "false");
- app_main_section.insert("ckanext.datapusher_plus.qsv_bin", "/usr/local/bin/qsvdp");
- app_main_section.insert("ckanext.datapusher_plus.preview_rows", "100");
- app_main_section.insert("ckanext.datapusher_plus.download_timeout", "300");
- app_main_section.insert(
- "ckanext.datapusher_plus.max_content_length",
- "1256000000000",
- );
- app_main_section.insert("ckanext.datapusher_plus.chunk_size", "16384");
- app_main_section.insert("ckanext.datapusher_plus.default_excel_sheet", "0");
- app_main_section.insert("ckanext.datapusher_plus.sort_and_dupe_check", "true");
- app_main_section.insert("ckanext.datapusher_plus.dedup", "false");
- app_main_section.insert("ckanext.datapusher_plus.unsafe_prefix", "unsafe_");
- app_main_section.insert("ckanext.datapusher_plus.reserved_colnames", "_id");
- app_main_section.insert("ckanext.datapusher_plus.prefer_dmy", "false");
- app_main_section.insert("ckanext.datapusher_plus.ignore_file_hash", "true");
- app_main_section.insert("ckanext.datapusher_plus.auto_index_threshold", "3");
- app_main_section.insert("ckanext.datapusher_plus.auto_index_dates", "true");
- app_main_section.insert("ckanext.datapusher_plus.auto_unique_index", "true");
- app_main_section.insert("ckanext.datapusher_plus.summary_stats_options", "");
- app_main_section.insert(
- "ckanext.datapusher_plus.add_summary_stats_resource",
- "false",
- );
- app_main_section.insert(
- "ckanext.datapusher_plus.summary_stats_with_preview",
- "false",
- );
- app_main_section.insert(
- "ckanext.datapusher_plus.qsv_stats_string_max_length",
- "32767",
- );
- app_main_section.insert(
- "ckanext.datapusher_plus.qsv_dates_whitelist",
- "date,time,due,open,close,created",
- );
- app_main_section.insert("ckanext.datapusher_plus.qsv_freq_limit", "10");
- app_main_section.insert("ckanext.datapusher_plus.auto_alias", "true");
- app_main_section.insert("ckanext.datapusher_plus.auto_alias_unique", "false");
- app_main_section.insert("ckanext.datapusher_plus.copy_readbuffer_size", "1048576");
- app_main_section.insert("ckanext.datapusher_plus.type_mapping", r#"{"String": "text", "Integer": "numeric","Float": "numeric","DateTime": "timestamp","Date": "date","NULL": "text"}"#);
- app_main_section.insert("ckanext.datapusher_plus.auto_spatial_simplication", "true");
- app_main_section.insert(
- "ckanext.datapusher_plus.spatial_simplication_relative_tolerance",
- "0.1",
- );
- app_main_section.insert("ckanext.datapusher_plus.latitude_fields", "latitude,lat");
- app_main_section.insert(
- "ckanext.datapusher_plus.longitude_fields",
- "longitude,long,lon",
- );
- app_main_section.insert(
- "ckanext.datapusher_plus.jinja2_bytecode_cache_dir",
- "/tmp/jinja2_butecode_cache",
- );
- app_main_section.insert("ckanext.datapusher_plus.auto_unzip_one_file", "true");
- app_main_section.insert(
- "ckanext.datapusher_plus.api_token",
- "",
- );
- app_main_section.insert(
- "ckanext.datapusher_plus.describeGPT_api_key",
- "",
- );
- app_main_section.insert("ckanext.datapusher_plus.file_bin", "/usr/bin/file");
- app_main_section.insert("ckanext.datapusher_plus.enable_druf", "false");
- app_main_section.insert("ckanext.datapusher_plus.enable_form_redirect", "true");
- conf.write_to_file("/etc/ckan/default/ckan.ini")?;
- let resource_formats_str = std::fs::read_to_string(
- "/usr/lib/ckan/default/src/ckan/ckan/config/resource_formats.json",
- )?;
- let mut resource_formats_val: serde_json::Value = serde_json::from_str(&resource_formats_str)?;
- let all_resource_formats = resource_formats_val
- .get_mut(0)
- .unwrap()
- .as_array_mut()
- .unwrap();
- all_resource_formats.push(json!([
- "TAB",
- "Tab Separated Values File",
- "text/tab-separated-values",
- []
- ]));
- std::fs::write(
- "/usr/lib/ckan/default/src/ckan/ckan/config/resource_formats.json",
- serde_json::to_string(&resource_formats_val)?,
- )?;
- cmd!(sh, "sudo locale-gen en_US.UTF-8").run()?;
- cmd!(sh, "sudo update-locale").run()?;
- let token_command_output = cmd!(
- sh,
- "ckan -c /etc/ckan/default/ckan.ini user token add {sysadmin_username} dpplus"
- )
- .read()?;
- let tail_output = cmd!(sh, "tail -n 1").stdin(token_command_output).read()?;
- let dpp_api_token = cmd!(sh, "tr -d '\t'").stdin(tail_output).read()?;
- cmd!(sh, "ckan config-tool /etc/ckan/default/ckan.ini ckanext.datapusher_plus.api_token={dpp_api_token}").env("LC_ALL", "en_US.UTF-8").run()?;
- cmd!(
- sh,
- "ckan -c /etc/ckan/default/ckan.ini db upgrade -p datapusher_plus"
- )
- .run()?;
- println!(
- "{}",
- success_text(format!("{step_prefix} Installed DataPusher+ extension.").as_str())
- );
- Ok(())
-}
diff --git a/src/styles.rs b/src/styles.rs
deleted file mode 100644
index 11234dd..0000000
--- a/src/styles.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-use owo_colors::{OwoColorize, Stream::Stdout};
-
-pub fn highlighted_text(text: &str) -> impl std::fmt::Display {
- format!(
- "{}",
- text.if_supports_color(Stdout, |t| t.on_blue().white())
- )
-}
-
-pub fn important_text(text: &str) -> impl std::fmt::Display {
- format!(
- "{}",
- text.if_supports_color(Stdout, |t| t.on_bright_red().white())
- )
-}
-
-pub fn step_text(text: &str) -> impl std::fmt::Display {
- format!(
- "{}",
- text.if_supports_color(Stdout, |t| t.on_magenta().white())
- )
-}
-
-pub fn success_text(text: &str) -> impl std::fmt::Display {
- format!(
- "{}",
- text.if_supports_color(Stdout, |t| t.on_green().white())
- )
-}