diff --git a/.bundle/config b/.bundle/config
new file mode 100644
index 0000000000000000000000000000000000000000..d13a8592ab60889a14160f0636117dd552209c0c
--- /dev/null
+++ b/.bundle/config
@@ -0,0 +1,2 @@
+---
+BUNDLE_JOBS: "4"
diff --git a/.puppet-lint.rc b/.puppet-lint.rc
new file mode 100644
index 0000000000000000000000000000000000000000..cc96ece0513d69709b87af611173e2a6e4532f62
--- /dev/null
+++ b/.puppet-lint.rc
@@ -0,0 +1 @@
+--relative
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000000000000000000000000000000000000..31e8248ff813e956702d5c67844aeb0e2affc917
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,519 @@
+---
+require:
+- rubocop-performance
+- rubocop-rspec
+AllCops:
+  DisplayCopNames: true
+  TargetRubyVersion: '2.5'
+  Include:
+  - "**/*.rb"
+  Exclude:
+  - bin/*
+  - ".vendor/**/*"
+  - "**/Gemfile"
+  - "**/Rakefile"
+  - pkg/**/*
+  - spec/fixtures/**/*
+  - vendor/**/*
+  - "**/Puppetfile"
+  - "**/Vagrantfile"
+  - "**/Guardfile"
+Layout/LineLength:
+  Description: People have wide screens, use them.
+  Max: 200
+RSpec/BeforeAfterAll:
+  Description: Beware of using after(:all) as it may cause state to leak between tests.
+    A necessary evil in acceptance testing.
+  Exclude:
+  - spec/acceptance/**/*.rb
+RSpec/HookArgument:
+  Description: Prefer explicit :each argument, matching existing module's style
+  EnforcedStyle: each
+RSpec/DescribeSymbol:
+  Exclude:
+  - spec/unit/facter/**/*.rb
+Style/BlockDelimiters:
+  Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to
+    be consistent then.
+  EnforcedStyle: braces_for_chaining
+Style/ClassAndModuleChildren:
+  Description: Compact style reduces the required amount of indentation.
+  EnforcedStyle: compact
+Style/EmptyElse:
+  Description: Enforce against empty else clauses, but allow `nil` for clarity.
+  EnforcedStyle: empty
+Style/FormatString:
+  Description: Following the main puppet project's style, prefer the % format format.
+  EnforcedStyle: percent
+Style/FormatStringToken:
+  Description: Following the main puppet project's style, prefer the simpler template
+    tokens over annotated ones.
+  EnforcedStyle: template
+Style/Lambda:
+  Description: Prefer the keyword for easier discoverability.
+  EnforcedStyle: literal
+Style/RegexpLiteral:
+  Description: Community preference. See https://github.com/voxpupuli/modulesync_config/issues/168
+  EnforcedStyle: percent_r
+Style/TernaryParentheses:
+  Description: Checks for use of parentheses around ternary conditions. Enforce parentheses
+    on complex expressions for better readability, but seriously consider breaking
+    it up.
+  EnforcedStyle: require_parentheses_when_complex
+Style/TrailingCommaInArguments:
+  Description: Prefer always trailing comma on multiline argument lists. This makes
+    diffs, and re-ordering nicer.
+  EnforcedStyleForMultiline: comma
+Style/TrailingCommaInArrayLiteral:
+  Description: Prefer always trailing comma on multiline literals. This makes diffs,
+    and re-ordering nicer.
+  EnforcedStyleForMultiline: comma
+Style/SymbolArray:
+  Description: Using percent style obscures symbolic intent of array's contents.
+  EnforcedStyle: brackets
+RSpec/MessageSpies:
+  EnforcedStyle: receive
+Style/Documentation:
+  Exclude:
+  - lib/puppet/parser/functions/**/*
+  - spec/**/*
+Style/WordArray:
+  EnforcedStyle: brackets
+Performance/AncestorsInclude:
+  Enabled: true
+Performance/BigDecimalWithNumericArgument:
+  Enabled: true
+Performance/BlockGivenWithExplicitBlock:
+  Enabled: true
+Performance/CaseWhenSplat:
+  Enabled: true
+Performance/ConstantRegexp:
+  Enabled: true
+Performance/MethodObjectAsBlock:
+  Enabled: true
+Performance/RedundantSortBlock:
+  Enabled: true
+Performance/RedundantStringChars:
+  Enabled: true
+Performance/ReverseFirst:
+  Enabled: true
+Performance/SortReverse:
+  Enabled: true
+Performance/Squeeze:
+  Enabled: true
+Performance/StringInclude:
+  Enabled: true
+Performance/Sum:
+  Enabled: true
+Style/CollectionMethods:
+  Enabled: true
+Style/MethodCalledOnDoEndBlock:
+  Enabled: true
+Style/StringMethods:
+  Enabled: true
+Bundler/InsecureProtocolSource:
+  Enabled: false
+Gemspec/DuplicatedAssignment:
+  Enabled: false
+Gemspec/OrderedDependencies:
+  Enabled: false
+Gemspec/RequiredRubyVersion:
+  Enabled: false
+Gemspec/RubyVersionGlobalsUsage:
+  Enabled: false
+Layout/ArgumentAlignment:
+  Enabled: false
+Layout/BeginEndAlignment:
+  Enabled: false
+Layout/ClosingHeredocIndentation:
+  Enabled: false
+Layout/EmptyComment:
+  Enabled: false
+Layout/EmptyLineAfterGuardClause:
+  Enabled: false
+Layout/EmptyLinesAroundArguments:
+  Enabled: false
+Layout/EmptyLinesAroundAttributeAccessor:
+  Enabled: false
+Layout/EndOfLine:
+  Enabled: false
+Layout/FirstArgumentIndentation:
+  Enabled: false
+Layout/HashAlignment:
+  Enabled: false
+Layout/HeredocIndentation:
+  Enabled: false
+Layout/LeadingEmptyLines:
+  Enabled: false
+Layout/SpaceAroundMethodCallOperator:
+  Enabled: false
+Layout/SpaceInsideArrayLiteralBrackets:
+  Enabled: false
+Layout/SpaceInsideReferenceBrackets:
+  Enabled: false
+Lint/BigDecimalNew:
+  Enabled: false
+Lint/BooleanSymbol:
+  Enabled: false
+Lint/ConstantDefinitionInBlock:
+  Enabled: false
+Lint/DeprecatedOpenSSLConstant:
+  Enabled: false
+Lint/DisjunctiveAssignmentInConstructor:
+  Enabled: false
+Lint/DuplicateElsifCondition:
+  Enabled: false
+Lint/DuplicateRequire:
+  Enabled: false
+Lint/DuplicateRescueException:
+  Enabled: false
+Lint/EmptyConditionalBody:
+  Enabled: false
+Lint/EmptyFile:
+  Enabled: false
+Lint/ErbNewArguments:
+  Enabled: false
+Lint/FloatComparison:
+  Enabled: false
+Lint/HashCompareByIdentity:
+  Enabled: false
+Lint/IdentityComparison:
+  Enabled: false
+Lint/InterpolationCheck:
+  Enabled: false
+Lint/MissingCopEnableDirective:
+  Enabled: false
+Lint/MixedRegexpCaptureTypes:
+  Enabled: false
+Lint/NestedPercentLiteral:
+  Enabled: false
+Lint/NonDeterministicRequireOrder:
+  Enabled: false
+Lint/OrderedMagicComments:
+  Enabled: false
+Lint/OutOfRangeRegexpRef:
+  Enabled: false
+Lint/RaiseException:
+  Enabled: false
+Lint/RedundantCopEnableDirective:
+  Enabled: false
+Lint/RedundantRequireStatement:
+  Enabled: false
+Lint/RedundantSafeNavigation:
+  Enabled: false
+Lint/RedundantWithIndex:
+  Enabled: false
+Lint/RedundantWithObject:
+  Enabled: false
+Lint/RegexpAsCondition:
+  Enabled: false
+Lint/ReturnInVoidContext:
+  Enabled: false
+Lint/SafeNavigationConsistency:
+  Enabled: false
+Lint/SafeNavigationWithEmpty:
+  Enabled: false
+Lint/SelfAssignment:
+  Enabled: false
+Lint/SendWithMixinArgument:
+  Enabled: false
+Lint/ShadowedArgument:
+  Enabled: false
+Lint/StructNewOverride:
+  Enabled: false
+Lint/ToJSON:
+  Enabled: false
+Lint/TopLevelReturnWithArgument:
+  Enabled: false
+Lint/TrailingCommaInAttributeDeclaration:
+  Enabled: false
+Lint/UnreachableLoop:
+  Enabled: false
+Lint/UriEscapeUnescape:
+  Enabled: false
+Lint/UriRegexp:
+  Enabled: false
+Lint/UselessMethodDefinition:
+  Enabled: false
+Lint/UselessTimes:
+  Enabled: false
+Metrics/AbcSize:
+  Enabled: false
+Metrics/BlockLength:
+  Enabled: false
+Metrics/BlockNesting:
+  Enabled: false
+Metrics/ClassLength:
+  Enabled: false
+Metrics/CyclomaticComplexity:
+  Enabled: false
+Metrics/MethodLength:
+  Enabled: false
+Metrics/ModuleLength:
+  Enabled: false
+Metrics/ParameterLists:
+  Enabled: false
+Metrics/PerceivedComplexity:
+  Enabled: false
+Migration/DepartmentName:
+  Enabled: false
+Naming/AccessorMethodName:
+  Enabled: false
+Naming/BlockParameterName:
+  Enabled: false
+Naming/HeredocDelimiterCase:
+  Enabled: false
+Naming/HeredocDelimiterNaming:
+  Enabled: false
+Naming/MemoizedInstanceVariableName:
+  Enabled: false
+Naming/MethodParameterName:
+  Enabled: false
+Naming/RescuedExceptionsVariableName:
+  Enabled: false
+Naming/VariableNumber:
+  Enabled: false
+Performance/BindCall:
+  Enabled: false
+Performance/DeletePrefix:
+  Enabled: false
+Performance/DeleteSuffix:
+  Enabled: false
+Performance/InefficientHashSearch:
+  Enabled: false
+Performance/UnfreezeString:
+  Enabled: false
+Performance/UriDefaultParser:
+  Enabled: false
+RSpec/Be:
+  Enabled: false
+RSpec/Capybara/CurrentPathExpectation:
+  Enabled: false
+RSpec/Capybara/FeatureMethods:
+  Enabled: false
+RSpec/Capybara/VisibilityMatcher:
+  Enabled: false
+RSpec/ContextMethod:
+  Enabled: false
+RSpec/ContextWording:
+  Enabled: false
+RSpec/DescribeClass:
+  Enabled: false
+RSpec/EmptyHook:
+  Enabled: false
+RSpec/EmptyLineAfterExample:
+  Enabled: false
+RSpec/EmptyLineAfterExampleGroup:
+  Enabled: false
+RSpec/EmptyLineAfterHook:
+  Enabled: false
+RSpec/ExampleLength:
+  Enabled: false
+RSpec/ExampleWithoutDescription:
+  Enabled: false
+RSpec/ExpectChange:
+  Enabled: false
+RSpec/ExpectInHook:
+  Enabled: false
+RSpec/FactoryBot/AttributeDefinedStatically:
+  Enabled: false
+RSpec/FactoryBot/CreateList:
+  Enabled: false
+RSpec/FactoryBot/FactoryClassName:
+  Enabled: false
+RSpec/HooksBeforeExamples:
+  Enabled: false
+RSpec/ImplicitBlockExpectation:
+  Enabled: false
+RSpec/ImplicitSubject:
+  Enabled: false
+RSpec/LeakyConstantDeclaration:
+  Enabled: false
+RSpec/LetBeforeExamples:
+  Enabled: false
+RSpec/MissingExampleGroupArgument:
+  Enabled: false
+RSpec/MultipleExpectations:
+  Enabled: false
+RSpec/MultipleMemoizedHelpers:
+  Enabled: false
+RSpec/MultipleSubjects:
+  Enabled: false
+RSpec/NestedGroups:
+  Enabled: false
+RSpec/PredicateMatcher:
+  Enabled: false
+RSpec/ReceiveCounts:
+  Enabled: false
+RSpec/ReceiveNever:
+  Enabled: false
+RSpec/RepeatedExampleGroupBody:
+  Enabled: false
+RSpec/RepeatedExampleGroupDescription:
+  Enabled: false
+RSpec/RepeatedIncludeExample:
+  Enabled: false
+RSpec/ReturnFromStub:
+  Enabled: false
+RSpec/SharedExamples:
+  Enabled: false
+RSpec/StubbedMock:
+  Enabled: false
+RSpec/UnspecifiedException:
+  Enabled: false
+RSpec/VariableDefinition:
+  Enabled: false
+RSpec/VoidExpect:
+  Enabled: false
+RSpec/Yield:
+  Enabled: false
+Security/Open:
+  Enabled: false
+Style/AccessModifierDeclarations:
+  Enabled: false
+Style/AccessorGrouping:
+  Enabled: false
+Style/AsciiComments:
+  Enabled: false
+Style/BisectedAttrAccessor:
+  Enabled: false
+Style/CaseLikeIf:
+  Enabled: false
+Style/ClassEqualityComparison:
+  Enabled: false
+Style/ColonMethodDefinition:
+  Enabled: false
+Style/CombinableLoops:
+  Enabled: false
+Style/CommentedKeyword:
+  Enabled: false
+Style/Dir:
+  Enabled: false
+Style/DoubleCopDisableDirective:
+  Enabled: false
+Style/EmptyBlockParameter:
+  Enabled: false
+Style/EmptyLambdaParameter:
+  Enabled: false
+Style/Encoding:
+  Enabled: false
+Style/EvalWithLocation:
+  Enabled: false
+Style/ExpandPathArguments:
+  Enabled: false
+Style/ExplicitBlockArgument:
+  Enabled: false
+Style/ExponentialNotation:
+  Enabled: false
+Style/FloatDivision:
+  Enabled: false
+Style/FrozenStringLiteralComment:
+  Enabled: false
+Style/GlobalStdStream:
+  Enabled: false
+Style/HashAsLastArrayItem:
+  Enabled: false
+Style/HashLikeCase:
+  Enabled: false
+Style/HashTransformKeys:
+  Enabled: false
+Style/HashTransformValues:
+  Enabled: false
+Style/IfUnlessModifier:
+  Enabled: false
+Style/KeywordParametersOrder:
+  Enabled: false
+Style/MinMax:
+  Enabled: false
+Style/MixinUsage:
+  Enabled: false
+Style/MultilineWhenThen:
+  Enabled: false
+Style/NegatedUnless:
+  Enabled: false
+Style/NumericPredicate:
+  Enabled: false
+Style/OptionalBooleanParameter:
+  Enabled: false
+Style/OrAssignment:
+  Enabled: false
+Style/RandomWithOffset:
+  Enabled: false
+Style/RedundantAssignment:
+  Enabled: false
+Style/RedundantCondition:
+  Enabled: false
+Style/RedundantConditional:
+  Enabled: false
+Style/RedundantFetchBlock:
+  Enabled: false
+Style/RedundantFileExtensionInRequire:
+  Enabled: false
+Style/RedundantRegexpCharacterClass:
+  Enabled: false
+Style/RedundantRegexpEscape:
+  Enabled: false
+Style/RedundantSelfAssignment:
+  Enabled: false
+Style/RedundantSort:
+  Enabled: false
+Style/RescueStandardError:
+  Enabled: false
+Style/SingleArgumentDig:
+  Enabled: false
+Style/SlicingWithRange:
+  Enabled: false
+Style/SoleNestedConditional:
+  Enabled: false
+Style/StderrPuts:
+  Enabled: false
+Style/StringConcatenation:
+  Enabled: false
+Style/Strip:
+  Enabled: false
+Style/SymbolProc:
+  Enabled: false
+Style/TrailingBodyOnClass:
+  Enabled: false
+Style/TrailingBodyOnMethodDefinition:
+  Enabled: false
+Style/TrailingBodyOnModule:
+  Enabled: false
+Style/TrailingCommaInHashLiteral:
+  Enabled: false
+Style/TrailingMethodEndStatement:
+  Enabled: false
+Style/UnpackFirst:
+  Enabled: false
+Lint/DuplicateBranch:
+  Enabled: false
+Lint/DuplicateRegexpCharacterClassElement:
+  Enabled: false
+Lint/EmptyBlock:
+  Enabled: false
+Lint/EmptyClass:
+  Enabled: false
+Lint/NoReturnInBeginEndBlocks:
+  Enabled: false
+Lint/ToEnumArguments:
+  Enabled: false
+Lint/UnexpectedBlockArity:
+  Enabled: false
+Lint/UnmodifiedReduceAccumulator:
+  Enabled: false
+Performance/CollectionLiteralInLoop:
+  Enabled: false
+Style/ArgumentsForwarding:
+  Enabled: false
+Style/CollectionCompact:
+  Enabled: false
+Style/DocumentDynamicEvalDefinition:
+  Enabled: false
+Style/NegatedIfElseCondition:
+  Enabled: false
+Style/NilLambda:
+  Enabled: false
+Style/RedundantArgument:
+  Enabled: false
+Style/SwapValues:
+  Enabled: false
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000000000000000000000000000000000000..fc28658599631b7120cffbab087dfa5c030e418e
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,63 @@
+source ENV['GEM_SOURCE'] || 'https://rubygems.org'
+
+def location_for(place_or_version, fake_version = nil)
+  git_url_regex = %r{\A(?<url>(https?|git)[:@][^#]*)(#(?<branch>.*))?}
+  file_url_regex = %r{\Afile:\/\/(?<path>.*)}
+
+  if place_or_version && (git_url = place_or_version.match(git_url_regex))
+    [fake_version, { git: git_url[:url], branch: git_url[:branch], require: false }].compact
+  elsif place_or_version && (file_url = place_or_version.match(file_url_regex))
+    ['>= 0', { path: File.expand_path(file_url[:path]), require: false }]
+  else
+    [place_or_version, { require: false }]
+  end
+end
+
+ruby_version_segments = Gem::Version.new(RUBY_VERSION.dup).segments
+minor_version = ruby_version_segments[0..1].join('.')
+
+group :development do
+  gem "json", '= 2.0.4',                                         require: false if Gem::Requirement.create('~> 2.4.2').satisfied_by?(Gem::Version.new(RUBY_VERSION.dup))
+  gem "json", '= 2.1.0',                                         require: false if Gem::Requirement.create(['>= 2.5.0', '< 2.7.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup))
+  gem "json", '= 2.3.0',                                         require: false if Gem::Requirement.create(['>= 2.7.0', '< 2.8.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup))
+  gem "puppet-module-posix-default-r#{minor_version}", '~> 1.0', require: false, platforms: [:ruby]
+  gem "puppet-module-posix-dev-r#{minor_version}", '~> 1.0',     require: false, platforms: [:ruby]
+  gem "puppet-module-win-default-r#{minor_version}", '~> 1.0',   require: false, platforms: [:mswin, :mingw, :x64_mingw]
+  gem "puppet-module-win-dev-r#{minor_version}", '~> 1.0',       require: false, platforms: [:mswin, :mingw, :x64_mingw]
+  gem "voxpupuli-puppet-lint-plugins", '>= 3.0',                 require: false
+end
+group :system_tests do
+  gem "puppet-module-posix-system-r#{minor_version}", '~> 1.0', require: false, platforms: [:ruby]
+  gem "puppet-module-win-system-r#{minor_version}", '~> 1.0',   require: false, platforms: [:mswin, :mingw, :x64_mingw]
+end
+
+puppet_version = ENV['PUPPET_GEM_VERSION']
+facter_version = ENV['FACTER_GEM_VERSION']
+hiera_version = ENV['HIERA_GEM_VERSION']
+
+gems = {}
+
+gems['puppet'] = location_for(puppet_version)
+
+# If facter or hiera versions have been specified via the environment
+# variables
+
+gems['facter'] = location_for(facter_version) if facter_version
+gems['hiera'] = location_for(hiera_version) if hiera_version
+
+gems.each do |gem_name, gem_params|
+  gem gem_name, *gem_params
+end
+
+# Evaluate Gemfile.local and ~/.gemfile if they exist
+extra_gemfiles = [
+  "#{__FILE__}.local",
+  File.join(Dir.home, '.gemfile'),
+]
+
+extra_gemfiles.each do |gemfile|
+  if File.file?(gemfile) && File.readable?(gemfile)
+    eval(File.read(gemfile), binding)
+  end
+end
+# vim: syntax=ruby
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000000000000000000000000000000000000..c1b46dd66f553799d1c932c8f240aa67b50619fb
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,511 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    CFPropertyList (2.3.6)
+    addressable (2.8.1)
+      public_suffix (>= 2.0.2, < 6.0)
+    ansi (1.5.0)
+    ast (2.4.2)
+    awesome_print (1.9.2)
+    aws-eventstream (1.2.0)
+    aws-partitions (1.709.0)
+    aws-sdk-core (3.170.0)
+      aws-eventstream (~> 1, >= 1.0.2)
+      aws-partitions (~> 1, >= 1.651.0)
+      aws-sigv4 (~> 1.5)
+      jmespath (~> 1, >= 1.6.1)
+    aws-sdk-ec2 (1.364.0)
+      aws-sdk-core (~> 3, >= 3.165.0)
+      aws-sigv4 (~> 1.1)
+    aws-sigv4 (1.5.2)
+      aws-eventstream (~> 1, >= 1.0.2)
+    bcrypt_pbkdf (1.1.0)
+    bindata (2.4.15)
+    bolt (3.26.2)
+      CFPropertyList (~> 2.2)
+      addressable (~> 2.5)
+      aws-sdk-ec2 (~> 1)
+      concurrent-ruby (~> 1.0)
+      ffi (>= 1.9.25, < 2.0.0)
+      hiera-eyaml (~> 3)
+      jwt (~> 2.2)
+      logging (~> 2.2)
+      minitar (~> 0.6)
+      net-scp (~> 1.2)
+      net-ssh (>= 4.0, < 7.0)
+      net-ssh-krb (~> 0.5)
+      orchestrator_client (~> 0.5)
+      puppet (>= 6.18.0)
+      puppet-resource_api (>= 1.8.1)
+      puppet-strings (>= 2.3.0, < 4.0)
+      puppetfile-resolver (>= 0.6.2, < 1.0)
+      r10k (~> 3.10)
+      ruby_smb (~> 1.0)
+      terminal-table (~> 3.0)
+      winrm (~> 2.0)
+      winrm-fs (~> 1.3)
+    builder (3.2.4)
+    codecov (0.6.0)
+      simplecov (>= 0.15, < 0.22)
+    coderay (1.1.3)
+    colored2 (3.1.2)
+    concurrent-ruby (1.1.10)
+    connection_pool (2.3.0)
+    cri (2.15.11)
+    deep_merge (1.2.2)
+    dependency_checker (0.3.0)
+      parallel
+      puppet_forge (>= 2.2, < 4.0)
+      rake (~> 13.0)
+      semantic_puppet (~> 1.0)
+    diff-lcs (1.5.0)
+    docile (1.4.0)
+    docker-api (2.2.0)
+      excon (>= 0.47.0)
+      multi_json
+    domain_name (0.5.20190701)
+      unf (>= 0.0.5, < 1.0.0)
+    ed25519 (1.3.0)
+    erubi (1.12.0)
+    excon (0.99.0)
+    facter (4.3.0)
+      hocon (~> 1.3)
+      thor (>= 1.0.1, < 2.0)
+    facterdb (1.21.0)
+      facter (< 5.0.0)
+      jgrep
+    faraday (1.10.3)
+      faraday-em_http (~> 1.0)
+      faraday-em_synchrony (~> 1.0)
+      faraday-excon (~> 1.1)
+      faraday-httpclient (~> 1.0)
+      faraday-multipart (~> 1.0)
+      faraday-net_http (~> 1.0)
+      faraday-net_http_persistent (~> 1.0)
+      faraday-patron (~> 1.0)
+      faraday-rack (~> 1.0)
+      faraday-retry (~> 1.0)
+      ruby2_keywords (>= 0.0.4)
+    faraday-em_http (1.0.0)
+    faraday-em_synchrony (1.0.0)
+    faraday-excon (1.1.0)
+    faraday-httpclient (1.0.1)
+    faraday-multipart (1.0.4)
+      multipart-post (~> 2)
+    faraday-net_http (1.0.1)
+    faraday-net_http_persistent (1.2.0)
+    faraday-patron (1.0.0)
+    faraday-rack (1.0.0)
+    faraday-retry (1.0.3)
+    faraday_middleware (1.2.0)
+      faraday (~> 1.0)
+    fast_gettext (2.2.0)
+    ffi (1.15.5)
+    ffi-compiler (1.0.1)
+      ffi (>= 1.0.0)
+      rake
+    forwardable (1.3.3)
+    gettext (3.4.3)
+      erubi
+      locale (>= 2.0.5)
+      prime
+      text (>= 1.3.0)
+    gettext-setup (1.1.0)
+      fast_gettext (~> 2.1)
+      gettext (~> 3.4)
+      locale
+    gssapi (1.3.1)
+      ffi (>= 1.0.1)
+    gyoku (1.4.0)
+      builder (>= 2.1.2)
+      rexml (~> 3.0)
+    hiera (3.12.0)
+    hiera-eyaml (3.3.0)
+      highline
+      optimist
+    highline (2.1.0)
+    hirb (0.7.3)
+    hocon (1.3.1)
+    honeycomb-beeline (2.11.0)
+      libhoney (>= 1.14.2)
+    http (5.1.1)
+      addressable (~> 2.8)
+      http-cookie (~> 1.0)
+      http-form_data (~> 2.2)
+      llhttp-ffi (~> 0.4.0)
+    http-accept (1.7.0)
+    http-cookie (1.0.5)
+      domain_name (~> 0.5)
+    http-form_data (2.3.0)
+    httpclient (2.8.3)
+    jgrep (1.5.4)
+    jmespath (1.6.2)
+    json (2.3.0)
+    json-schema (3.0.0)
+      addressable (>= 2.8)
+    jwt (2.2.3)
+    libhoney (2.2.0)
+      addressable (~> 2.0)
+      excon
+      http (>= 2.0, < 6.0)
+    little-plugger (1.1.4)
+    llhttp-ffi (0.4.0)
+      ffi-compiler (~> 1.0)
+      rake (~> 13.0)
+    locale (2.1.3)
+    log4r (1.1.10)
+    logging (2.3.1)
+      little-plugger (~> 1.1)
+      multi_json (~> 1.14)
+    metaclass (0.0.4)
+    metadata-json-lint (3.0.2)
+      json-schema (>= 2.8, < 4.0)
+      spdx-licenses (~> 1.0)
+    method_source (1.0.0)
+    mime-types (3.4.1)
+      mime-types-data (~> 3.2015)
+    mime-types-data (3.2022.0105)
+    minitar (0.9)
+    mocha (1.1.0)
+      metaclass (~> 0.0.1)
+    molinillo (0.8.0)
+    multi_json (1.15.0)
+    multipart-post (2.3.0)
+    net-http-persistent (4.0.1)
+      connection_pool (~> 2.2)
+    net-scp (1.2.1)
+      net-ssh (>= 2.6.5)
+    net-ssh (6.1.0)
+    net-ssh-krb (0.5.1)
+      gssapi (~> 1.3.0)
+      net-ssh (>= 2.0)
+    net-telnet (0.1.1)
+    netrc (0.11.0)
+    nori (2.6.0)
+    optimist (3.0.1)
+    orchestrator_client (0.6.1)
+      faraday (~> 1.4)
+      net-http-persistent (~> 4.0)
+    parallel (1.22.1)
+    parallel_tests (3.13.0)
+      parallel
+    parser (3.2.1.0)
+      ast (~> 2.4.1)
+    pathspec (1.0.0)
+    pluginator (1.5.0)
+    prime (0.1.2)
+      forwardable
+      singleton
+    pry (0.14.2)
+      coderay (~> 1.1)
+      method_source (~> 1.0)
+    public_suffix (5.0.1)
+    puppet (7.22.0)
+      concurrent-ruby (~> 1.0, < 1.2.0)
+      deep_merge (~> 1.0)
+      facter (> 2.0.1, < 5)
+      fast_gettext (>= 1.1, < 3)
+      hiera (>= 3.2.1, < 4)
+      locale (~> 2.1)
+      multi_json (~> 1.10)
+      puppet-resource_api (~> 1.5)
+      scanf (~> 1.0)
+      semantic_puppet (~> 1.0)
+    puppet-blacksmith (6.1.1)
+      puppet-modulebuilder (~> 0.2)
+      rest-client (~> 2.0)
+    puppet-debugger (1.3.0)
+      awesome_print (~> 1.7)
+      bundler
+      facterdb (>= 0.4.0)
+      pluginator (~> 1.5.0)
+      puppet (>= 5.5)
+      rb-readline (>= 0.5.5)
+      table_print (>= 1.0.0)
+      tty-pager (~> 0.13)
+    puppet-lint (2.5.2)
+    puppet-lint-absolute_classname-check (3.1.0)
+      puppet-lint (>= 1.0, < 4)
+    puppet-lint-anchor-check (1.1.0)
+      puppet-lint (>= 1.1, < 4)
+    puppet-lint-classes_and_types_beginning_with_digits-check (1.0.0)
+      puppet-lint (>= 1.0, < 3.0)
+    puppet-lint-file_ensure-check (1.1.0)
+      puppet-lint (>= 1.0, < 4)
+    puppet-lint-leading_zero-check (1.0.0)
+      puppet-lint (>= 1.0, < 3.0)
+    puppet-lint-legacy_facts-check (1.0.4)
+      puppet-lint (~> 2.4)
+    puppet-lint-lookup_in_parameter-check (1.0.0)
+      puppet-lint (~> 2.0)
+    puppet-lint-manifest_whitespace-check (0.2.9)
+      puppet-lint (>= 1.0, < 4)
+    puppet-lint-optional_default-check (1.1.0)
+      puppet-lint (>= 2.1, < 4)
+    puppet-lint-param-docs (1.7.6)
+      puppet-lint (>= 1.1, < 4.0)
+    puppet-lint-param-types (0.0.1)
+      puppet-lint (>= 1.1, < 3.0)
+    puppet-lint-params_empty_string-check (1.0.0)
+      puppet-lint (~> 2.5)
+    puppet-lint-resource_reference_syntax (1.2.0)
+      puppet-lint (>= 1.0, < 4)
+    puppet-lint-strict_indent-check (2.1.0)
+      puppet-lint (>= 1.0, < 4)
+    puppet-lint-top_scope_facts-check (1.0.1)
+      puppet-lint (~> 2.0)
+    puppet-lint-topscope-variable-check (1.1.0)
+      puppet-lint (~> 2.0)
+    puppet-lint-trailing_comma-check (1.0.0)
+      puppet-lint (>= 1.0, < 4)
+    puppet-lint-unquoted_string-check (2.2.0)
+      puppet-lint (>= 2.1, < 4)
+    puppet-lint-variable_contains_upcase (1.3.0)
+      puppet-lint (>= 1.0, < 4)
+    puppet-lint-version_comparison-check (1.1.0)
+      puppet-lint (>= 1.0, < 4)
+    puppet-module-posix-default-r2.7 (1.1.1)
+    puppet-module-posix-dev-r2.7 (1.1.1)
+      bcrypt_pbkdf (~> 1.0)
+      codecov (~> 0.2)
+      concurrent-ruby (!= 1.1.6)
+      dependency_checker (~> 0.2)
+      ed25519 (~> 1.2)
+      facterdb (>= 0.8.1, < 2.0.0)
+      metadata-json-lint (>= 2.0.2, < 4.0.0)
+      mocha (>= 1.0.0, < 1.2.0)
+      parallel_tests (~> 3.4)
+      pry (~> 0.10)
+      puppet-blacksmith (~> 6.0)
+      puppet-debugger (~> 1.0)
+      puppet-lint (>= 2.3.0, < 3.0.0)
+      puppet-resource_api (~> 1.8)
+      puppet-strings (~> 2.0)
+      puppet-syntax (~> 3.0)
+      puppetlabs_spec_helper (>= 2.9.0, < 4.0.0)
+      rainbow (~> 2.0)
+      rspec-puppet (>= 2.3.2, < 3.0.0)
+      rspec-puppet-facts (>= 1.10.0, < 3)
+      rspec_junit_formatter (~> 0.2)
+      rubocop (= 1.6.1)
+      rubocop-performance (= 1.9.1)
+      rubocop-rspec (= 2.0.1)
+      serverspec (~> 2.41)
+      simplecov (< 0.19.0)
+      simplecov-console (~> 0.4.2)
+      specinfra (= 2.82.2)
+    puppet-module-posix-system-r2.7 (1.1.1)
+      puppet_litmus (~> 0.20)
+    puppet-modulebuilder (0.3.0)
+      minitar (~> 0.9)
+      pathspec (>= 0.2.1, < 2.0.0)
+    puppet-resource_api (1.8.14)
+      hocon (>= 1.0)
+    puppet-strings (2.9.0)
+      rgen
+      yard (~> 0.9.5)
+    puppet-syntax (3.3.0)
+      puppet (>= 5)
+      rake
+    puppet_forge (3.2.0)
+      faraday (~> 1.3)
+      faraday_middleware (~> 1.0)
+      minitar
+      semantic_puppet (~> 1.0)
+    puppet_litmus (0.34.4)
+      bolt (~> 3.0)
+      docker-api (>= 1.34, < 3.0.0)
+      honeycomb-beeline
+      parallel
+      puppet-modulebuilder (>= 0.2.1, < 1.0.0)
+      retryable (~> 3.0)
+      rspec
+      rspec_honeycomb_formatter
+      tty-spinner (>= 0.5.0, < 1.0.0)
+    puppetfile-resolver (0.6.2)
+      molinillo (~> 0.6)
+      semantic_puppet (~> 1.0)
+    puppetlabs_spec_helper (3.0.0)
+      mocha (~> 1.0)
+      pathspec (>= 0.2.1, < 1.1.0)
+      puppet-lint (~> 2.0)
+      puppet-syntax (>= 2.0, < 4)
+      rspec-puppet (~> 2.0)
+    r10k (3.15.4)
+      colored2 (= 3.1.2)
+      cri (>= 2.15.10)
+      fast_gettext (>= 1.1.0, < 3.0.0)
+      gettext (>= 3.0.2, < 4.0.0)
+      gettext-setup (>= 0.24, < 2.0.0)
+      jwt (~> 2.2.3)
+      log4r (= 1.1.10)
+      minitar (~> 0.9)
+      multi_json (~> 1.10)
+      puppet_forge (>= 2.3.0, < 4.0.0)
+    rainbow (2.2.2)
+      rake
+    rake (13.0.6)
+    rb-readline (0.5.5)
+    regexp_parser (2.7.0)
+    rest-client (2.1.0)
+      http-accept (>= 1.7.0, < 2.0)
+      http-cookie (>= 1.0.2, < 2.0)
+      mime-types (>= 1.16, < 4.0)
+      netrc (~> 0.8)
+    retryable (3.0.5)
+    rexml (3.2.5)
+    rgen (0.9.1)
+    rspec (3.12.0)
+      rspec-core (~> 3.12.0)
+      rspec-expectations (~> 3.12.0)
+      rspec-mocks (~> 3.12.0)
+    rspec-core (3.12.1)
+      rspec-support (~> 3.12.0)
+    rspec-expectations (3.12.2)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.12.0)
+    rspec-its (1.3.0)
+      rspec-core (>= 3.0.0)
+      rspec-expectations (>= 3.0.0)
+    rspec-mocks (3.12.3)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.12.0)
+    rspec-puppet (2.12.0)
+      rspec
+    rspec-puppet-facts (2.0.5)
+      facter
+      facterdb (>= 0.5.0)
+      puppet
+    rspec-support (3.12.0)
+    rspec_honeycomb_formatter (0.2.1)
+      honeycomb-beeline
+      rspec-core (~> 3.0)
+    rspec_junit_formatter (0.6.0)
+      rspec-core (>= 2, < 4, != 2.12.0)
+    rubocop (1.6.1)
+      parallel (~> 1.10)
+      parser (>= 2.7.1.5)
+      rainbow (>= 2.2.2, < 4.0)
+      regexp_parser (>= 1.8, < 3.0)
+      rexml
+      rubocop-ast (>= 1.2.0, < 2.0)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (>= 1.4.0, < 2.0)
+    rubocop-ast (1.24.1)
+      parser (>= 3.1.1.0)
+    rubocop-performance (1.9.1)
+      rubocop (>= 0.90.0, < 2.0)
+      rubocop-ast (>= 0.4.0)
+    rubocop-rspec (2.0.1)
+      rubocop (~> 1.0)
+      rubocop-ast (>= 1.1.0)
+    ruby-progressbar (1.11.0)
+    ruby2_keywords (0.0.5)
+    ruby_smb (1.1.0)
+      bindata
+      rubyntlm
+      windows_error
+    rubyntlm (0.6.3)
+    rubyzip (2.3.2)
+    scanf (1.0.0)
+    semantic_puppet (1.0.4)
+    serverspec (2.42.1)
+      multi_json
+      rspec (~> 3.0)
+      rspec-its
+      specinfra (~> 2.72)
+    sfl (2.3)
+    simplecov (0.18.5)
+      docile (~> 1.1)
+      simplecov-html (~> 0.11)
+    simplecov-console (0.4.2)
+      ansi
+      hirb
+      simplecov
+    simplecov-html (0.12.3)
+    singleton (0.1.1)
+    spdx-licenses (1.3.0)
+    specinfra (2.82.2)
+      net-scp
+      net-ssh (>= 2.7)
+      net-telnet (= 0.1.1)
+      sfl
+    strings (0.2.1)
+      strings-ansi (~> 0.2)
+      unicode-display_width (>= 1.5, < 3.0)
+      unicode_utils (~> 1.4)
+    strings-ansi (0.2.0)
+    table_print (1.5.7)
+    terminal-table (3.0.2)
+      unicode-display_width (>= 1.1.1, < 3)
+    text (1.3.1)
+    thor (1.2.1)
+    tty-cursor (0.7.1)
+    tty-pager (0.14.0)
+      strings (~> 0.2.0)
+      tty-screen (~> 0.8)
+    tty-screen (0.8.1)
+    tty-spinner (0.9.3)
+      tty-cursor (~> 0.7)
+    unf (0.1.4)
+      unf_ext
+    unf_ext (0.0.8.2)
+    unicode-display_width (1.8.0)
+    unicode_utils (1.4.0)
+    voxpupuli-puppet-lint-plugins (3.1.0)
+      puppet-lint (>= 2.5.0)
+      puppet-lint-absolute_classname-check (~> 3.1)
+      puppet-lint-anchor-check (~> 1.1)
+      puppet-lint-classes_and_types_beginning_with_digits-check (~> 1.0)
+      puppet-lint-file_ensure-check (~> 1.1)
+      puppet-lint-leading_zero-check (~> 1.0)
+      puppet-lint-legacy_facts-check (>= 1.0.4, < 2.0.0)
+      puppet-lint-lookup_in_parameter-check (~> 1.0)
+      puppet-lint-manifest_whitespace-check (~> 0.2.7, < 1.0.0)
+      puppet-lint-optional_default-check (~> 1.1)
+      puppet-lint-param-docs (>= 1.7.6, < 2.0.0)
+      puppet-lint-param-types (~> 0.0)
+      puppet-lint-params_empty_string-check (~> 1.0)
+      puppet-lint-resource_reference_syntax (~> 1.1)
+      puppet-lint-strict_indent-check (~> 2.1)
+      puppet-lint-top_scope_facts-check (>= 1.0.1, < 2.0.0)
+      puppet-lint-topscope-variable-check (~> 1.1)
+      puppet-lint-trailing_comma-check (~> 1.0)
+      puppet-lint-unquoted_string-check (~> 2.2)
+      puppet-lint-variable_contains_upcase (~> 1.2)
+      puppet-lint-version_comparison-check (~> 1.1)
+    webrick (1.7.0)
+    windows_error (0.1.5)
+    winrm (2.3.6)
+      builder (>= 2.1.2)
+      erubi (~> 1.8)
+      gssapi (~> 1.2)
+      gyoku (~> 1.0)
+      httpclient (~> 2.2, >= 2.2.0.2)
+      logging (>= 1.6.1, < 3.0)
+      nori (~> 2.0)
+      rubyntlm (~> 0.6.0, >= 0.6.3)
+    winrm-fs (1.3.5)
+      erubi (~> 1.8)
+      logging (>= 1.6.1, < 3.0)
+      rubyzip (~> 2.0)
+      winrm (~> 2.0)
+    yard (0.9.28)
+      webrick (~> 1.7.0)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  json (= 2.3.0)
+  puppet (= 7.22.0)
+  puppet-module-posix-default-r2.7 (~> 1.0)
+  puppet-module-posix-dev-r2.7 (~> 1.0)
+  puppet-module-posix-system-r2.7 (~> 1.0)
+  puppet-module-win-default-r2.7 (~> 1.0)
+  puppet-module-win-dev-r2.7 (~> 1.0)
+  puppet-module-win-system-r2.7 (~> 1.0)
+  voxpupuli-puppet-lint-plugins (>= 3.0)
+
+BUNDLED WITH
+   2.1.4
diff --git a/files/matrix-appservice-irc.service b/files/matrix-appservice-irc.service
new file mode 100644
index 0000000000000000000000000000000000000000..ad25afcf1a3563f8cb5a618eae9eae0900a9ce26
--- /dev/null
+++ b/files/matrix-appservice-irc.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=
+
+[Service]
+Type=simple
+Restart=always
+EnvironmentFile=/etc/default/matrix-appservice-irc
+WorkingDirectory=/home/lysroot/matrix-appservice-irc
+ExecStart=/usr/bin/node /home/lysroot/matrix-appservice-irc/app.js -c $CONFIG -f $REGISTRATION -p $PORT
+
+[Install]
+WantedBy=multi-user.target
diff --git a/hiera.yaml b/hiera.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..545fff32722a61409075e87fd0003fd8b2d01f5f
--- /dev/null
+++ b/hiera.yaml
@@ -0,0 +1,21 @@
+---
+version: 5
+
+defaults:  # Used for any hierarchy level that omits these keys.
+  datadir: data         # This path is relative to hiera.yaml's directory.
+  data_hash: yaml_data  # Use the built-in YAML backend.
+
+hierarchy:
+  - name: "osfamily/major release"
+    paths:
+        # Used to distinguish between Debian and Ubuntu
+      - "os/%{facts.os.name}/%{facts.os.release.major}.yaml"
+      - "os/%{facts.os.family}/%{facts.os.release.major}.yaml"
+        # Used for Solaris
+      - "os/%{facts.os.family}/%{facts.kernelrelease}.yaml"
+  - name: "osfamily"
+    paths:
+      - "os/%{facts.os.name}.yaml"
+      - "os/%{facts.os.family}.yaml"
+  - name: 'common'
+    path: 'common.yaml'
diff --git a/manifests/appservice/irc.pp b/manifests/appservice/irc.pp
new file mode 100644
index 0000000000000000000000000000000000000000..9c292e36da277aab7cd0a189972a75bc95ffefcd
--- /dev/null
+++ b/manifests/appservice/irc.pp
@@ -0,0 +1,108 @@
+# https://matrix-org.github.io/matrix-appservice-irc/latest/usage
+# https://github.com/matrix-org/matrix-appservice-irc/blob/develop/config.sample.yaml
+define matrix::appservice::irc (
+  String $server_name = $name,
+  String $domain,
+  Stdlib::Port $port = 6697,
+  Hash[String, Hash] $mappings = {},
+  String $network_id,
+  Boolean $ssl = true,
+  Boolean $ssl_self_sign = false,
+  Boolean $sasl = true,
+  Boolean $allow_expired_certs = false,
+  Boolean $send_connection_messages = true,
+  Hash $quit_debounce = {},
+  Struct[{'o' => Integer, 'v' => Integer}] $move_power_map = {
+    'o' => 50, 'v' => 1,
+  },
+  Hash $bot_config = {
+    'enabled' => false,
+  },
+  Hash $private_messages = {
+    'enabled'  => true,
+    'federate' => true,
+  },
+  Hash $dynamic_channels = {
+    'enabled' => false,
+  },
+  Hash $membership_lists = {
+    'enabled' => false,
+  },
+  Hash $matrix_clients = {
+    'userTemplate' => '@$NICK[irc]',
+    'displayName'  => '$NICK',
+    'joinAttempts' => -1,
+  },
+  Hash $irc_clients = {
+    'nickTemplate'     => '$DISPLAY[m]',
+    'allowNickChanges' => true,
+    'maxClients'       => 30,
+  },
+  Hash $ipv6 = {
+    'only' => false,
+  },
+  Integer $line_limit = 15,
+  Enum['mixd', 'reverse-mixd'] $realname_format = 'mixd',
+  Hash $kick_on = {
+    'channelJoinFailure'   => true,
+    'ircConnectionFailure' => true,
+    'userQuit'             => true,
+  },
+
+
+) {
+  include matrix::appservice::irc::setup
+  $service_file = "/etc/systemd/system/${service_name}"
+  # NOT configurable, hard coded in service file
+  $service_env_file = '/etc/default/matrix-appservice-irc'
+
+  file { $matrix_appservice_irc_path:
+    ensure => directory,
+  }
+
+  $debug_api = $irc_bridge_debug_port ? {
+    Undef  => {},
+    String => { 'debugApi' => {
+      'enabled' => false,
+      'port'    => $irc_bridge_debug_port,
+    }},
+  }
+
+  $libera_settings = {
+    'name'                   => $server_name,
+    'networkId'              => $network_id,
+    'port'                   => $port,
+    'ssl'                    => $ssl,
+    'sslselfsign'            => $ssl_self_sign,
+    'sasl'                   => $sasl,
+    'allowExpiredCerts'      => $allow_expired_certs,
+    'sendConnectionMessages' => $send_connection_messages,
+    'quitDebounce'           => $quit_debounce,
+    'modePowerMap'           => $mode_power_map,
+    'botConfig'              => $bot_config,
+    'privateMessages'        => $private_messages,
+    'dynamicChannels'        => $dynamic_channels,
+    'membershipLists'        => $membership_lists,
+    'mappings'               => $mappings,
+    'matrixClients'          => $matrix_clients,
+    'ircClients'             => $irc_clients,
+    'ipv6'                   => $ipv6,
+    'lineLimit'              => $line_limit,
+    'realnameFormat'         => $realname_format,
+    'kickOn'                 => $kick_on,
+  }
+
+  $hash = {
+    $domain => $libera_settings,
+  }
+
+  $data = hash2yaml($hash, {'indentation' => 2})
+  .split("\n")
+  .map |$line| { "  ${line}" }
+  .join("\n")
+
+  concat::fragment { '':
+    content => $data,
+    target  => $matrix::appservice::irc::setup::config_file,
+  }
+}
diff --git a/manifests/appservice/irc/setup.pp b/manifests/appservice/irc/setup.pp
new file mode 100644
index 0000000000000000000000000000000000000000..596393c065dba2fbaf89716bcd3ad846ae1ed500
--- /dev/null
+++ b/manifests/appservice/irc/setup.pp
@@ -0,0 +1,117 @@
+class matrix::appservice::irc::setup (
+  String $homeserver_url,
+  Stdlib::Port $bind_port = 8090,
+  String $db_name = 'irc_bridge',
+  String $db_user,
+  String $db_host = 'localhost',
+  Variant[String, Sensitive[String]] $db_pass,
+  Boolean $manage_db_user = true,
+  Optional[Stdlib::Port] $db_port = undef,
+  Variant[String, Sensitive[String]] $passkey,
+  String $config_file = '/etc/matrix-synapse/appservice-irc.yaml',
+  String $service_name = 'matrix-appservice-irc.service',
+  String $matrix_appservice_irc_path = '/home/lysroot/matrix-appservice-irc',
+  String $registration_file = "${matrix_appservice_irc_path}/appservice-registration-irc.yaml",
+  String $config_file = "${matrix_appservice_irc_path}/config.yaml",
+  String $user_id = 'my_bot',
+  Stdlib::Port $listen_port = 9999,
+) {
+
+  # TODO fetch code
+
+  postgresql::server::database { $db_name:
+    encoding => 'UTF-8',
+    locale   => 'C',
+    owner    => $db_user,
+  }
+
+  if $manage_db_user {
+    postgresql::server::role { $db_user:
+      update_password => true,
+      login           => true,
+      password_hash   => postgresql::postgresql_password(
+        $db_user, $db_pass, false
+      ),
+    }
+  }
+
+  $db_pass_ = $db_pass ? {
+    String => $db_pass,
+    _      => $db_pass.unwrap,
+  }
+
+  $passkey = 'passkey.pem'
+  exec { 'Generate password encryption key':
+    command => ['openssl', 'genpkey',
+                '-out', $passkey,
+                '-outform', 'PEM',
+                '-algorithm', 'RSA',
+                '-pkeyeopt', 'rsa_keygen_bits:2048'],
+    creates => "${matrix_appservice_irc_path}/${passkey}",
+    path    => ['/usr/bin', '/bin'],
+  }
+
+  concat { $config_file:
+    notify => [
+      Service[$synapse::service_name],
+      Service[$service_name],
+    ],
+  }
+
+  concat::fragment {
+    content => epp("${module_name}/appservice_irc.yaml.epp")
+  }
+
+  exec { 'Generate appservice file':
+    command     => [
+      'node', 'app.js',
+      '--generate-registration',
+      '--file', $registration_file,
+      '--url', "http://localhost:${listen_port}",
+      '--config', $config_file,
+      '--localpart', $user_id,
+    ],
+    cwd         => $matrix_appservice_irc_path,
+    # creates     => $registration_file,
+    path        => ['/bin','/usr/bin'],
+    subscribe   => File[$config_file],
+    refreshonly => true,
+    notify      => [
+      Service[$synapse::service_name],
+      Service[$service_name],
+    ],
+  }
+
+  file { $service_file:
+    ensure => file,
+    source => "puppet:///modules/${module_name}/matrix-appservice-irc.service",
+  } ~> Exec['systemctl daemon-reload']
+
+  exec { 'systemctl daemon-reload':
+    refreshonly => true,
+    provider    => shell,
+  }
+
+  file { $service_env_file:
+    content => @("EOF")
+    CONFIG=${config_file}
+    REGISTRATION=${registration_file}
+    PORT=${listen_port}
+    | EOF
+  }
+
+  service { $service_name:
+    ensure    => running,
+    enable    => true,
+    require   => [
+      File[$service_file],
+      File[$service_env_file],
+    ],
+    subscribe => [
+      File[$service_file],
+      File[$service_env_file],
+      Exec['systemctl daemon-reload']
+    ],
+  }
+
+}
diff --git a/manifests/config.pp b/manifests/config.pp
new file mode 100644
index 0000000000000000000000000000000000000000..8beb4dbe6dffa9d537aae12bfa086ee1a7a9dae1
--- /dev/null
+++ b/manifests/config.pp
@@ -0,0 +1,113 @@
+class matrix::config (
+) {
+
+  file { [
+    '/etc/matrix-synapse',
+    '/etc/matrix-synapse/conf.d',
+  ]:
+    ensure => directory,
+  }
+
+  file { $log_conf_file:
+    content        => hash2yaml({
+      'version'    => 1,
+      'formatters' => {
+        'verbose' => {
+          format => '%(module)s %(message)s'
+        },
+        'precise' => {
+          'format' => '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s',
+        }
+      },
+      'handlers'   => {
+        'sys-logger6' => {
+          'class'     => 'logging.handlers.SysLogHandler',
+          'address'   => '/dev/log',
+          'facility'  => 'local6',
+          'formatter' => 'verbose',
+        },
+        'console'     => {
+          'class'     => 'logging.StreamHandler',
+          'formatter' => 'precise',
+        }
+      },
+      'loggers'    => {
+        '.'  => {
+          'handlers'  => [
+            'sys-logger6',
+            'console',
+          ],
+          'level'     => 'DEBUG',
+          'propagate' => true,
+        }
+      },
+    }, {
+      'header' => '# This file is controlled by puppet',
+    })
+  }
+  file { $log_conf_file:
+    content        => hash2yaml({
+      'version'    => 1,
+      'formatters' => {
+        'verbose' => {
+          format => '%(module)s %(message)s'
+        },
+        'precise' => {
+          'format' => '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s',
+        }
+      },
+      'handlers'   => {
+        'sys-logger6' => {
+          'class'     => 'logging.handlers.SysLogHandler',
+          'address'   => '/dev/log',
+          'facility'  => 'local6',
+          'formatter' => 'verbose',
+        },
+        'console'     => {
+          'class'     => 'logging.StreamHandler',
+          'formatter' => 'precise',
+        }
+      },
+      'loggers'    => {
+        '.'  => {
+          'handlers'  => [
+            'sys-logger6',
+            'console',
+          ],
+          'level'     => 'DEBUG',
+          'propagate' => true,
+        }
+      },
+    }, {
+      'header' => '# This file is controlled by puppet',
+    })
+  }
+
+  {
+    'trusted_key_servers' => [
+      {
+        'server_name' => 'matrix.org',
+      },
+    ],
+    'supress_key_server_warning' => true,
+    'password_providers'         => [
+    ],
+    'registrations_require_3pid' => [
+      'email',
+    ],
+    'email'        => {
+      'notif_from' => '%(app)s@matrix.lysator.liu.se',
+      'enable_tls' => false,
+      'subjects'   => $email_subjects,
+    },
+    'app_service_config_file' => [
+      '/home/lysroot/matrix-appservice-irc/appservice-registration-irc.yaml',
+    ],
+    'log_config' => [
+      '/etc/matrix-synapse/log.yaml',
+    ],
+    'oidc_providers' => [
+      $matrix::oidc::keycloak::config,
+    ],
+  }
+}
diff --git a/manifests/database/postgres.pp b/manifests/database/postgres.pp
new file mode 100644
index 0000000000000000000000000000000000000000..6b47576a2b9e3a74dc4caed478931e7d64a47e79
--- /dev/null
+++ b/manifests/database/postgres.pp
@@ -0,0 +1,20 @@
+class matrix::database::postgres (
+  String $user,
+  String $db_name,
+  Sensitive[String] $db_pass,
+) {
+
+  create_resources('Postgresql::Server::Role',
+    $db_users, {
+      update_password => true,
+      login           => true,
+    })
+
+  postgresql::server::database { $db_name:
+    encoding => 'UTF-8'
+    locale   => 'C',
+    owner    => $user,
+    # before Class['synapse']
+  }
+
+}
diff --git a/manifests/init.pp b/manifests/init.pp
new file mode 100644
index 0000000000000000000000000000000000000000..98a1ad291cc6a8ae9a436bc97faa50fbd32569ab
--- /dev/null
+++ b/manifests/init.pp
@@ -0,0 +1,48 @@
+class matrix (
+  String $servername,
+  Stdlib::Port $listen_port = 8008,
+  Boolean $registrastration_enabled = false,
+  String $package_name = 'matrix-synapse-py3',
+  String $service_name = 'matrix-synapse',
+) {
+
+  require apt
+  apt::source { 'synapse':
+    locatino => 'https://packages.matrix.org/debian/',
+    release  => $facts['os']['distro']['codename'],
+    repos    => 'main',
+    key      => 'AAF9AE843A7584B5A3E4CD2BCF45A512DE2DA058',
+    include  => {
+      'deb'  => true,
+    },
+  }
+
+  package { $package_name:
+    ensure => installed,
+    alias  => 'matrix-synapse',
+  }
+
+  service { $service_name:
+    ensure => running,
+    enable => true,
+  }
+
+
+  include matrix::config
+  include ::postgresql::server
+
+  postgres::server::database { $db_name:
+    encoding => 'UTF-8',
+    locale   => 'C',
+    owner    => $db_user,
+    # before => Class['synapse']
+  }
+
+  postgresql::server::role { $db_user:
+    update_password => true,
+    login           => true,
+    password_hash   => postgresql::postgresql_password(
+      $db_user, $db_pass, false
+    ),
+  }
+}
diff --git a/manifests/oidc/keycloak.pp b/manifests/oidc/keycloak.pp
new file mode 100644
index 0000000000000000000000000000000000000000..0fcd31cb659e1ae89479200e55a99be17dc068fe
--- /dev/null
+++ b/manifests/oidc/keycloak.pp
@@ -0,0 +1,29 @@
+define matrix::oidc::keycloak (
+  String $description,
+  String $issuer,
+  String $client_id,
+  Variant[String, Sensitive[String]] $client_secret,
+  Boolean $backchannel_logout = true,
+  Struct[{'localpart_template' => String, 'display_name_template' => String}] $user_mapping = {
+    'localpart_template'    => '{{user.preferred_username}}',
+    'display_name_template' => '{{user.name}}',
+  },
+) {
+  $client_secret_ = $client_secret ? {
+    String => $client_secret,
+    _      => $client_secret.unwrap,
+  }
+
+  $config = {
+    'idp_id'                     => 'keycloak',
+    'idp_name'                   => $description,
+    'issuer'                     => $issuer,
+    'client_id'                  => $client_id,
+    'client_secret'              => $client_secret_,
+    'scopes'                     => ['openid', 'profile'],
+    'backchannel_logout_enabled' => $backchannel_logout,
+    'user_mapping_provider' => {
+      'config' => $user_mapping,
+    }
+  }
+}
diff --git a/manifests/revproxy.pp b/manifests/revproxy.pp
new file mode 100644
index 0000000000000000000000000000000000000000..84cd0489f592669cf7421634171c6c82adb344e8
--- /dev/null
+++ b/manifests/revproxy.pp
@@ -0,0 +1,69 @@
+# https://matrix-org.github.io/synapse/latest/reverse_proxy.html?highlight=nginx#nginx
+class matrix::revproxy (
+) {
+
+  include ::letsencrypt 
+  letsencrypt::cert { $matrix::servername:
+    include_self  => true,
+    authenticator => 'nginx',
+    email         => 'hugo@lysator.liu.se',
+    config        => {
+      'post-hook' => 'nginx -s reload',
+    }
+  }
+
+  nginx::resource::server { $matrix::servername:
+    *                    => letsencrypt::conf::nginx($matrix::servername),
+    ipv6_enable          => true,
+    use_default_location => false,
+    client_max_body_size => $matrix::max_upload_size,
+  }
+
+  $well_known_client = to_json({
+    'm.homeserver' => {
+      'bare_url' => $matrix::servername,
+    },
+  })
+
+  $well_known_server = to_json({
+    'm.server' => $matrix::servername,
+  })
+
+
+  nginx::resource::location { '~ ^(/_matrix|/_synapse/client)':
+    server      => $matrix::servername,
+    index_files => [],
+    *           => letsencrypt::conf::nginx::location($matrix::servarname),
+    proxy              => "http://localhost:${matrix::listen_port}",
+    proxy_http_version => '1.1',
+    proxy_set_header   => [
+      'X-Forwarded-For $remote_addr',
+      'X-Forwarded-Proto $scheme',
+      'Host $host',
+    ],
+  }
+
+  nginx::resource::location {
+    default:
+      server      => $matrix::servername,
+      index_files => [],
+      add_header  => {
+        'Access-Control-Allow-Origin' => '*',
+      },
+      *           => letsencrypt::conf::nginx::location($matrix::servarname),
+      ;
+    '/.well-known/matrix/server':
+      location_cfg_append => {
+        'default_type' => 'application/json',
+        'return'       => "200 '${well_known_server}'",
+      },
+      ;
+    '/.well-known/matrix/client':
+      location_cfg_append => {
+        'default_type' => 'application/json',
+        'return'       => "200 '${well_known_client}'",
+      },
+      ;
+  }
+
+}
diff --git a/metadata.json b/metadata.json
new file mode 100644
index 0000000000000000000000000000000000000000..e30d0b91c897bf8a001f944d93cf665df628dbf6
--- /dev/null
+++ b/metadata.json
@@ -0,0 +1,31 @@
+{
+	"name": "Lysator-matrix",
+	"version": "1.0.0",
+	"author": "hugo",
+	"license": "Apache-2.0",
+	"source": "https://git.lysator.liu.se/lysator/puppet/matrix",
+	"summary": "Configures Matrix synapse and more.",
+	"operatingsystem_support": [
+		{
+			"operatingsystem": "Debian",
+			"operatingsystemrelease": [
+				"11"
+			]
+		}
+	],
+	"dependencies": [
+      "postgresql",
+      "concat",
+      "stdlib",
+      "hugonikanor-letsencrypt"
+	],
+	"requirements": [
+		{
+			"name": "puppet",
+			"version_requirement": ">= 6.21.0 < 8.0.0"
+		}
+	],
+	"pdk-version": "2.5.0",
+	"template-url": "pdk-default#2.5.0",
+	"template-ref": "tags/2.5.0-0-g369d483"
+}
diff --git a/pdk.yaml b/pdk.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4bef4bd0f902390cfeb13e968d1879c74963b50e
--- /dev/null
+++ b/pdk.yaml
@@ -0,0 +1,2 @@
+---
+ignore: []
diff --git a/templates/appservice_irc.yaml.epp b/templates/appservice_irc.yaml.epp
new file mode 100644
index 0000000000000000000000000000000000000000..7ff96dff26b19570151e4ba9bb659ecef867d012
--- /dev/null
+++ b/templates/appservice_irc.yaml.epp
@@ -0,0 +1,50 @@
+---
+# FILE CONTROLLED BY PUPPET
+homeserver:
+  url: https://<%= $homeserver_name %>
+  domain: <%= $homeserver_name %>
+  bindPort: <%= $bind_port %>
+ircService:
+  servers:
+    # TODO $servers
+  bridgeInfoState:
+    enabled: true
+    initial: false
+  ident:
+    enabled: true
+    port: 1113
+    address: '::'
+  logging:
+    level: debug
+    logfile: debug.log
+    errfile: errors.log
+    toConsole: true
+    maxFiles: 5
+  metrics:
+    enabled: false
+  provisioning:
+    enabled: false
+  passwordEncryptionKeyPath: "<%= $passkey %>"
+  matrixHandler:
+    eventCacheSize: 4096
+    shortReplyTemplate: '$NICK: $REPLY'
+    longReplyTemplate: '<$NICK> "$ORIGINAL" <- $REPLY',
+    shortReplyTresholdSeconds: 300
+  ircHandler:
+    mapIrcMentionsToMatrix: 'on'
+  perRoomConfig:
+    enabled: false
+  banLists:
+    rooms: []
+  debugApi:
+    enabled: <%= !! $debug_port %>
+    <% if $debug_port { %>port: <%= $debug_port %><% } %>
+advanced:
+  maxHttpSockets: 1000
+  maxTxnSize: 10000000
+sentry:
+  enabled: false
+database:
+  engine: postgres
+  connectionString: "postgres://${db_user}:${db_pass_}@${db_host}/${db_name}"
+